@xopcai/xopc 0.0.147 → 0.0.149

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 (143) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{activity-store-DXeSSlw1.js → activity-store-B4s0ipx2.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-CFdJKSjY.js → agent-avatar-dicebear-BSccACje.js} +1 -1
  5. package/dist/gateway/static/root/assets/agents-DZDOcgz5.js +2 -0
  6. package/dist/gateway/static/root/assets/{api-Bs69B5zA.js → api-BMdUsNCV.js} +1 -1
  7. package/dist/gateway/static/root/assets/{app-management-settings-panel-ag1ViXT_.js → app-management-settings-panel-DrLGhYkI.js} +1 -1
  8. package/dist/gateway/static/root/assets/{appearance-settings-C6iMpwOM.js → appearance-settings-BNasnY9D.js} +1 -1
  9. package/dist/gateway/static/root/assets/apps-page-Cmr-Hvm9.js +1 -0
  10. package/dist/gateway/static/root/assets/{archive-plugin-Dwi3mIBy.js → archive-plugin-BQ17IK9x.js} +1 -1
  11. package/dist/gateway/static/root/assets/{attachment-load-BaCDoq40.js → attachment-load-HXvgWHXu.js} +1 -1
  12. package/dist/gateway/static/root/assets/{automation-suggestion-card-CwqtFgnw.js → automation-suggestion-card-CjIWpsch.js} +1 -1
  13. package/dist/gateway/static/root/assets/automations-page-TWgUAI8t.js +6 -0
  14. package/dist/gateway/static/root/assets/{binary-plugins-DoAYhsxa.js → binary-plugins-DifWJzNZ.js} +1 -1
  15. package/dist/gateway/static/root/assets/{block-editor-Bw72pO7S.js → block-editor-DtRzIkPU.js} +4 -4
  16. package/dist/gateway/static/root/assets/browser-settings-page-DBsFItW_.js +8 -0
  17. package/dist/gateway/static/root/assets/{capability-presets-api-vdbAR65Q.js → capability-presets-api-DazKjWW1.js} +1 -1
  18. package/dist/gateway/static/root/assets/{capability-presets-settings-panel-BAsKbJgm.js → capability-presets-settings-panel-Cvrw4fD1.js} +2 -2
  19. package/dist/gateway/static/root/assets/{channel-recipient-api-rTG26jpV.js → channel-recipient-api-Bea6if_6.js} +1 -1
  20. package/dist/gateway/static/root/assets/channels-settings-BBkzrBYy.js +1 -0
  21. package/dist/gateway/static/root/assets/channels-status-swr-CuU6ez3y.js +1 -0
  22. package/dist/gateway/static/root/assets/{connector-logo-DIMP4NWY.js → connector-logo-BjbYbJjQ.js} +1 -1
  23. package/dist/gateway/static/root/assets/connectors-page-DLsZCAw7.js +2 -0
  24. package/dist/gateway/static/root/assets/{desktop-pet-kQq2e0BN.js → desktop-pet-C6V-PHwi.js} +1 -1
  25. package/dist/gateway/static/root/assets/{desktop-pet-settings-B6vtozMY.js → desktop-pet-settings-5Fcz47XQ.js} +1 -1
  26. package/dist/gateway/static/root/assets/{directory-picker-path-field-Dn5-e3v4.js → directory-picker-path-field-C8zZNUzC.js} +1 -1
  27. package/dist/gateway/static/root/assets/{dreaming-settings-BzwIujPG.js → dreaming-settings-gJSw7Adk.js} +3 -3
  28. package/dist/gateway/static/root/assets/{extension-debug-page-D_e_hsEy.js → extension-debug-page-DFE5ADJ0.js} +1 -1
  29. package/dist/gateway/static/root/assets/{extension-page-D15CWHBd.js → extension-page-CfR-aW8G.js} +1 -1
  30. package/dist/gateway/static/root/assets/{extension-settings-page-D4P0n1om.js → extension-settings-page-CFIEbs2L.js} +1 -1
  31. package/dist/gateway/static/root/assets/{gateway-config-api-B4qZctjG.js → gateway-config-api-B6M_ra8U.js} +1 -1
  32. package/dist/gateway/static/root/assets/gateway-settings-BrxzyVW2.js +1 -0
  33. package/dist/gateway/static/root/assets/{gateway-startup-retry-BpgMunEb.js → gateway-startup-retry-DNA3OOzV.js} +1 -1
  34. package/dist/gateway/static/root/assets/{goal-create-dialog-DrVHKXHq.js → goal-create-dialog-DRVRLc5Z.js} +1 -1
  35. package/dist/gateway/static/root/assets/goal-detail-page-8F7NElzP.js +4 -0
  36. package/dist/gateway/static/root/assets/{goals-config-api-B6YJB_ct.js → goals-config-api-D88opgcs.js} +1 -1
  37. package/dist/gateway/static/root/assets/goals-page-BaoLiV8G.js +2 -0
  38. package/dist/gateway/static/root/assets/{goals-settings-BROWCIcL.js → goals-settings-D07UL8Jo.js} +1 -1
  39. package/dist/gateway/static/root/assets/{heartbeat-settings-CoCwIOIg.js → heartbeat-settings-YCSNOcXC.js} +1 -1
  40. package/dist/gateway/static/root/assets/{index-CxkNzw3e.js → index-CH7XN6KY.js} +43 -43
  41. package/dist/gateway/static/root/assets/index-Dfnl9tp_.css +1 -0
  42. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Bm_AIYQ7.js → keyboard-shortcuts-settings-BHnh5OoY.js} +1 -1
  43. package/dist/gateway/static/root/assets/local-app-workbench-page-Boe8n4zH.js +2 -0
  44. package/dist/gateway/static/root/assets/local-apps-page-CKvtLzsF.js +1 -0
  45. package/dist/gateway/static/root/assets/{locale-store-Bp8joZck.js → locale-store-BneNAR8R.js} +1 -1
  46. package/dist/gateway/static/root/assets/logs-page-Cb4Uptct.js +7 -0
  47. package/dist/gateway/static/root/assets/{markdown-split-Dy7QpaAz.js → markdown-split-BVSFG99p.js} +1 -1
  48. package/dist/gateway/static/root/assets/{media-plugins-3Lxliy9D.js → media-plugins-DdOjIVk3.js} +1 -1
  49. package/dist/gateway/static/root/assets/{messages-CQGpD-dC.js → messages-t1yPaVCt.js} +5 -5
  50. package/dist/gateway/static/root/assets/models-hub-panel-CNiUKZUV.js +2 -0
  51. package/dist/gateway/static/root/assets/{notes-page-BJtT4vSf.js → notes-page-ZbLR3awn.js} +1 -1
  52. package/dist/gateway/static/root/assets/notes-workbench-C9__VkD6.js +4 -0
  53. package/dist/gateway/static/root/assets/{page-tabs-DKB6RDre.js → page-tabs-DMlQERNg.js} +1 -1
  54. package/dist/gateway/static/root/assets/{preview-open-alternatives-Br_YhYHp.js → preview-open-alternatives-CbiJRPxK.js} +1 -1
  55. package/dist/gateway/static/root/assets/project-detail-page-CZhuB5kO.js +1 -0
  56. package/dist/gateway/static/root/assets/projects-page-BOq4WVdR.js +1 -0
  57. package/dist/gateway/static/root/assets/remote-access-hub-BxGq0ebQ.js +1 -0
  58. package/dist/gateway/static/root/assets/{save-bar-controls-BgIE9BBh.js → save-bar-controls-BZTj3hxl.js} +1 -1
  59. package/dist/gateway/static/root/assets/{schedule-field-BXDP-oMz.js → schedule-field-CxYDObjT.js} +1 -1
  60. package/dist/gateway/static/root/assets/{schema-form-Dhv-SmjG.js → schema-form-CBbXgpKQ.js} +1 -1
  61. package/dist/gateway/static/root/assets/sessions-page-B6gSU_Au.js +1 -0
  62. package/dist/gateway/static/root/assets/settings-advanced-gate-DwuBL834.js +1 -0
  63. package/dist/gateway/static/root/assets/{settings-collapsible-section-CUTExnlK.js → settings-collapsible-section-BySKZ8rp.js} +1 -1
  64. package/dist/gateway/static/root/assets/{settings-form-section-CmnDhsf0.js → settings-form-section-CjINay6t.js} +1 -1
  65. package/dist/gateway/static/root/assets/{settings-loading-skeleton-B79dq-XY.js → settings-loading-skeleton-vitElc-n.js} +1 -1
  66. package/dist/gateway/static/root/assets/{settings-page-Db8uAv3X.js → settings-page-BNrVwuL7.js} +1 -1
  67. package/dist/gateway/static/root/assets/{settings-page-layout-DaFwstQw.js → settings-page-layout-HdlQOOqp.js} +1 -1
  68. package/dist/gateway/static/root/assets/setup-status-panel-BJeA4mmk.js +1 -0
  69. package/dist/gateway/static/root/assets/{share-preview-page-Bi00uXzV.js → share-preview-page-DC-eQONB.js} +1 -1
  70. package/dist/gateway/static/root/assets/shares-settings-DB2KYA2_.js +1 -0
  71. package/dist/gateway/static/root/assets/skill-reload-api-DQtZiejw.js +1 -0
  72. package/dist/gateway/static/root/assets/skills-page-Pm0fNe_p.js +2 -0
  73. package/dist/gateway/static/root/assets/{system-settings-panel-Cxaph7hW.js → system-settings-panel-CVa90q2f.js} +1 -1
  74. package/dist/gateway/static/root/assets/{theme-store-eCTN1RpG.js → theme-store-D1cOpgsy.js} +1 -1
  75. package/dist/gateway/static/root/assets/{typed-models-lib-CcZ_tyFY.js → typed-models-lib-DIKF9vpz.js} +1 -1
  76. package/dist/gateway/static/root/assets/{use-channel-catalog-C4PWbNoi.js → use-channel-catalog-DnvpI0Ys.js} +1 -1
  77. package/dist/gateway/static/root/assets/user-context-page-DnLqQyRG.js +1 -0
  78. package/dist/gateway/static/root/assets/{user-profile-fields-editor-CPOO9Jz5.js → user-profile-fields-editor-y8gTmkPX.js} +1 -1
  79. package/dist/gateway/static/root/assets/{user-profile-settings-panel-C8cgtSF7.js → user-profile-settings-panel-C3pvqe28.js} +1 -1
  80. package/dist/gateway/static/root/assets/{utils-CiZK2KwH.js → utils-BK2vwu_3.js} +1 -1
  81. package/dist/gateway/static/root/assets/voice-api-key-field-BiFIIWC4.js +1 -0
  82. package/dist/gateway/static/root/assets/work-discovery-page-D2-_bC0J.js +1 -0
  83. package/dist/gateway/static/root/assets/work-item-detail-page-B0FPG4eJ.js +1 -0
  84. package/dist/gateway/static/root/assets/work-page-BDeVxts_.js +1 -0
  85. package/dist/gateway/static/root/assets/workflow-run-setup-panel-BPzS67fU.js +3 -0
  86. package/dist/gateway/static/root/assets/workflows-page-xPhmrRLs.js +11 -0
  87. package/dist/gateway/static/root/index.html +7 -7
  88. package/dist/package.js +1 -1
  89. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  90. package/dist/src/gateway/hono/routes/work-discovery.js +32 -0
  91. package/dist/src/gateway/hono/routes/work-discovery.js.map +1 -1
  92. package/dist/src/storage/sqlite/migrations/046_work_discovery_feedback.sql +13 -0
  93. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  94. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  95. package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
  96. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  97. package/dist/src/storage/sqlite/schema.js +1 -1
  98. package/dist/src/work-discovery/analyzer.js +23 -4
  99. package/dist/src/work-discovery/analyzer.js.map +1 -1
  100. package/dist/src/work-discovery/index.js +2 -2
  101. package/dist/src/work-discovery/probe.d.ts +6 -0
  102. package/dist/src/work-discovery/probe.js +12 -1
  103. package/dist/src/work-discovery/probe.js.map +1 -1
  104. package/dist/src/work-discovery/repository.d.ts +7 -1
  105. package/dist/src/work-discovery/repository.js +35 -2
  106. package/dist/src/work-discovery/repository.js.map +1 -1
  107. package/dist/src/work-discovery/service.d.ts +6 -1
  108. package/dist/src/work-discovery/service.js +64 -5
  109. package/dist/src/work-discovery/service.js.map +1 -1
  110. package/dist/src/work-discovery/types.d.ts +21 -0
  111. package/dist/src/workflows/service/workflow-run-service.js +1 -1
  112. package/package.json +1 -1
  113. package/dist/gateway/static/root/assets/agents-DKjbRpbA.js +0 -2
  114. package/dist/gateway/static/root/assets/apps-page-CpX3lBFF.js +0 -1
  115. package/dist/gateway/static/root/assets/automations-page-DlupOxFQ.js +0 -6
  116. package/dist/gateway/static/root/assets/browser-settings-page-BwLoUBcZ.js +0 -8
  117. package/dist/gateway/static/root/assets/channels-settings-CxFWy0cE.js +0 -1
  118. package/dist/gateway/static/root/assets/channels-status-swr-OKlqTtKJ.js +0 -1
  119. package/dist/gateway/static/root/assets/connectors-page-BjQQwX62.js +0 -2
  120. package/dist/gateway/static/root/assets/gateway-settings-BvndV7TP.js +0 -1
  121. package/dist/gateway/static/root/assets/goal-detail-page-PHlCwv68.js +0 -4
  122. package/dist/gateway/static/root/assets/goals-page-Hg9uyO6e.js +0 -2
  123. package/dist/gateway/static/root/assets/index-DjCSOyaN.css +0 -1
  124. package/dist/gateway/static/root/assets/local-app-workbench-page-BJWFcxwG.js +0 -2
  125. package/dist/gateway/static/root/assets/local-apps-page-BsiZMrEf.js +0 -1
  126. package/dist/gateway/static/root/assets/logs-page-DOmK61ey.js +0 -7
  127. package/dist/gateway/static/root/assets/models-hub-panel-DGCZom4o.js +0 -2
  128. package/dist/gateway/static/root/assets/notes-workbench-B_bN7cOR.js +0 -4
  129. package/dist/gateway/static/root/assets/project-detail-page-Dq8JouJj.js +0 -1
  130. package/dist/gateway/static/root/assets/projects-page-DvfL7fSe.js +0 -1
  131. package/dist/gateway/static/root/assets/remote-access-hub-DqwMXSXP.js +0 -1
  132. package/dist/gateway/static/root/assets/sessions-page-B_mOZkQk.js +0 -1
  133. package/dist/gateway/static/root/assets/settings-advanced-gate-BHGmH6YS.js +0 -1
  134. package/dist/gateway/static/root/assets/setup-status-panel-BUSEj0KK.js +0 -1
  135. package/dist/gateway/static/root/assets/shares-settings-DzsTYfuX.js +0 -1
  136. package/dist/gateway/static/root/assets/skill-reload-api-DSsdU081.js +0 -1
  137. package/dist/gateway/static/root/assets/skills-page-DeB1tQZL.js +0 -2
  138. package/dist/gateway/static/root/assets/user-context-page-QTVMx_hG.js +0 -1
  139. package/dist/gateway/static/root/assets/voice-api-key-field-b-YZtvUu.js +0 -1
  140. package/dist/gateway/static/root/assets/work-discovery-page-4zVDOl2G.js +0 -1
  141. package/dist/gateway/static/root/assets/work-item-detail-page-CHe2tfkc.js +0 -1
  142. package/dist/gateway/static/root/assets/workflow-run-setup-panel-8SLCYeXE.js +0 -3
  143. package/dist/gateway/static/root/assets/workflows-page-DEOFqiWn.js +0 -11
@@ -11,7 +11,7 @@
11
11
  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?v=20260713-1" />
12
12
  <link rel="manifest" href="/site.webmanifest?v=20260713-1" />
13
13
  <title>xopc</title>
14
- <script type="module" crossorigin src="/assets/index-CxkNzw3e.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-CH7XN6KY.js"></script>
15
15
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-aKtaBQYM.js">
16
16
  <link rel="modulepreload" crossorigin href="/assets/preload-helper-zJ_50EbN.js">
17
17
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-BOUWij0V.js">
@@ -24,14 +24,14 @@
24
24
  <link rel="modulepreload" crossorigin href="/assets/check-BODqo66o.js">
25
25
  <link rel="modulepreload" crossorigin href="/assets/circle-alert-RzVebuX2.js">
26
26
  <link rel="modulepreload" crossorigin href="/assets/middleware-Pd0e8Igk.js">
27
- <link rel="modulepreload" crossorigin href="/assets/messages-CQGpD-dC.js">
28
- <link rel="modulepreload" crossorigin href="/assets/locale-store-Bp8joZck.js">
27
+ <link rel="modulepreload" crossorigin href="/assets/messages-t1yPaVCt.js">
28
+ <link rel="modulepreload" crossorigin href="/assets/locale-store-BneNAR8R.js">
29
29
  <link rel="modulepreload" crossorigin href="/assets/config-reload-event-Bjh2zoKp.js">
30
30
  <link rel="modulepreload" crossorigin href="/assets/copy-to-clipboard-HOJ79TWv.js">
31
- <link rel="modulepreload" crossorigin href="/assets/gateway-startup-retry-BpgMunEb.js">
32
- <link rel="modulepreload" crossorigin href="/assets/activity-store-DXeSSlw1.js">
33
- <link rel="modulepreload" crossorigin href="/assets/theme-store-eCTN1RpG.js">
34
- <link rel="stylesheet" crossorigin href="/assets/index-DjCSOyaN.css">
31
+ <link rel="modulepreload" crossorigin href="/assets/gateway-startup-retry-DNA3OOzV.js">
32
+ <link rel="modulepreload" crossorigin href="/assets/activity-store-B4s0ipx2.js">
33
+ <link rel="modulepreload" crossorigin href="/assets/theme-store-D1cOpgsy.js">
34
+ <link rel="stylesheet" crossorigin href="/assets/index-Dfnl9tp_.css">
35
35
  </head>
36
36
  <body>
37
37
  <div id="root"></div>
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.0.147";
2
+ var version = "0.0.149";
3
3
  //#endregion
4
4
  export { version };
5
5
 
@@ -52,7 +52,7 @@ async function checkDatabaseSchema(ctx) {
52
52
  id: "database-schema",
53
53
  label: "Database schema",
54
54
  status: "pass",
55
- message: `SQLite schema v${status.dbVersion} (current release v45).`,
55
+ message: `SQLite schema v${status.dbVersion} (current release v46).`,
56
56
  hints: []
57
57
  };
58
58
  }
@@ -137,6 +137,38 @@ function registerWorkDiscoveryRoutes(authenticated, deps) {
137
137
  error: "Run not found"
138
138
  }, 404);
139
139
  });
140
+ authenticated.post("/api/work-discovery/runs/:runId/recognition-feedback", async (c) => {
141
+ const body = await c.req.json().catch(() => null);
142
+ const decision = stringField(body, "decision");
143
+ if (![
144
+ "confirmed",
145
+ "corrected",
146
+ "different_goal",
147
+ "dismissed"
148
+ ].includes(decision)) return c.json({
149
+ ok: false,
150
+ error: "Invalid recognition decision"
151
+ }, 400);
152
+ try {
153
+ const run = await service.submitRecognitionFeedback({
154
+ runId: c.req.param("runId"),
155
+ decision,
156
+ ...stringField(body, "correctedIntent") ? { correctedIntent: stringField(body, "correctedIntent") } : {}
157
+ });
158
+ return run ? c.json({
159
+ ok: true,
160
+ run
161
+ }) : c.json({
162
+ ok: false,
163
+ error: "Completed run not found"
164
+ }, 404);
165
+ } catch (error) {
166
+ return c.json({
167
+ ok: false,
168
+ error: error instanceof Error ? error.message : String(error)
169
+ }, 400);
170
+ }
171
+ });
140
172
  authenticated.post("/api/work-discovery/runs/:runId/suggestions/:suggestionId/select", (c) => {
141
173
  return service.selectSuggestion(c.req.param("runId"), c.req.param("suggestionId")) ? c.json({ ok: true }) : c.json({
142
174
  ok: false,
@@ -1 +1 @@
1
- {"version":3,"file":"work-discovery.js","names":[],"sources":["../../../../../src/gateway/hono/routes/work-discovery.ts"],"sourcesContent":["import type { Hono } from 'hono';\n\nimport { getAgentDefaultModelRef } from '../../../config/schema.js';\nimport { isLocalModelBaseUrl } from '../../../providers/model-call.js';\nimport { resolveModel } from '../../../providers/index.js';\nimport { previewWorkDiscoveryRoot, WORK_DISCOVERY_SCAN_POLICY_VERSION } from '../../../work-discovery/probe.js';\nimport { WorkDiscoveryService } from '../../../work-discovery/service.js';\nimport type { WorkDiscoverySource } from '../../../work-discovery/types.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nconst services = new WeakMap<AuthenticatedRouteDeps['service'], WorkDiscoveryService>();\n\nfunction workDiscoveryService(deps: AuthenticatedRouteDeps): WorkDiscoveryService {\n const existing = services.get(deps.service);\n if (existing) return existing;\n const service = new WorkDiscoveryService({\n projects: deps.service.projects,\n sessions: deps.service.sessionIndexInstance,\n getConfig: () => deps.service.currentConfig,\n emit: (type, payload) => deps.service.emit(type, payload),\n });\n services.set(deps.service, service);\n return service;\n}\n\nfunction stringField(body: unknown, field: string): string {\n if (!body || typeof body !== 'object') return '';\n const value = (body as Record<string, unknown>)[field];\n return typeof value === 'string' ? value.trim() : '';\n}\n\nexport function registerWorkDiscoveryRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const service = workDiscoveryService(deps);\n const limited = deps.strictRateLimitMiddleware;\n\n authenticated.get('/api/onboarding/work-discovery', (c) => c.json({\n enabled: service.isEnabled(),\n state: service.getOnboardingState(),\n }));\n\n authenticated.patch('/api/onboarding/work-discovery', async (c) => {\n const body = await c.req.json().catch(() => null);\n if (stringField(body, 'status') !== 'dismissed') {\n return c.json({ ok: false, error: 'Only dismissed status is accepted' }, 400);\n }\n return c.json({ ok: true, state: service.dismissOnboarding() });\n });\n\n authenticated.post('/api/work-discovery/preview', limited, async (c) => {\n if (!service.isEnabled()) return c.json({ ok: false, error: 'Work discovery is disabled' }, 404);\n const body = await c.req.json().catch(() => null);\n const rootPath = stringField(body, 'rootPath');\n if (!rootPath) return c.json({ ok: false, error: 'Missing rootPath' }, 400);\n try {\n const preview = await previewWorkDiscoveryRoot(rootPath);\n const modelRef = getAgentDefaultModelRef(deps.service.currentConfig);\n if (!modelRef) return c.json({ ok: false, error: 'No default model configured' }, 409);\n const model = resolveModel(modelRef);\n return c.json({\n ok: true,\n preview: {\n ...preview,\n exists: true,\n readable: true,\n provider: model.provider,\n remoteModel: !isLocalModelBaseUrl(model.baseUrl),\n policyVersion: WORK_DISCOVERY_SCAN_POLICY_VERSION,\n },\n });\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 400);\n }\n });\n\n authenticated.post('/api/work-discovery/runs', limited, async (c) => {\n if (!service.isEnabled()) return c.json({ ok: false, error: 'Work discovery is disabled' }, 404);\n const body = await c.req.json().catch(() => null);\n const rootPath = stringField(body, 'rootPath');\n const idempotencyKey = stringField(body, 'idempotencyKey');\n const requestedSource = stringField(body, 'source');\n const source: WorkDiscoverySource = requestedSource === 'manual_selected_directory'\n ? 'manual_selected_directory'\n : 'onboarding_selected_directory';\n if (!rootPath || !idempotencyKey) {\n return c.json({ ok: false, error: 'Missing rootPath or idempotencyKey' }, 400);\n }\n try {\n const run = await service.startRun({ rootPath, source, idempotencyKey });\n return c.json({ ok: true, run }, 202);\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 400);\n }\n });\n\n authenticated.get('/api/work-discovery/runs/:runId', (c) => {\n const run = service.getRun(c.req.param('runId'));\n return run ? c.json({ ok: true, run }) : c.json({ ok: false, error: 'Run not found' }, 404);\n });\n\n authenticated.post('/api/work-discovery/runs/:runId/cancel', (c) => {\n const run = service.cancelRun(c.req.param('runId'));\n return run ? c.json({ ok: true, run }) : c.json({ ok: false, error: 'Run not found' }, 404);\n });\n\n authenticated.post('/api/work-discovery/runs/:runId/retry', (c) => {\n const run = service.retryRun(c.req.param('runId'));\n return run ? c.json({ ok: true, run }) : c.json({ ok: false, error: 'Run not found' }, 404);\n });\n\n authenticated.post('/api/work-discovery/runs/:runId/suggestions/:suggestionId/select', (c) => {\n const run = service.selectSuggestion(c.req.param('runId'), c.req.param('suggestionId'));\n return run ? c.json({ ok: true }) : c.json({ ok: false, error: 'Suggestion not found' }, 404);\n });\n}\n"],"mappings":";;;;;;aAEoE;gBAET;AAM3D,MAAM,2BAAW,IAAI,SAAkE;AAEvF,SAAS,qBAAqB,MAAoD;CAChF,MAAM,WAAW,SAAS,IAAI,KAAK,QAAQ;AAC3C,KAAI,SAAU,QAAO;CACrB,MAAM,UAAU,IAAI,qBAAqB;EACvC,UAAU,KAAK,QAAQ;EACvB,UAAU,KAAK,QAAQ;EACvB,iBAAiB,KAAK,QAAQ;EAC9B,OAAO,MAAM,YAAY,KAAK,QAAQ,KAAK,MAAM,QAAQ;EAC1D,CAAC;AACF,UAAS,IAAI,KAAK,SAAS,QAAQ;AACnC,QAAO;;AAGT,SAAS,YAAY,MAAe,OAAuB;AACzD,KAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;CAC9C,MAAM,QAAS,KAAiC;AAChD,QAAO,OAAO,UAAU,WAAW,MAAM,MAAM,GAAG;;AAGpD,SAAgB,4BAA4B,eAAqB,MAAoC;CACnG,MAAM,UAAU,qBAAqB,KAAK;CAC1C,MAAM,UAAU,KAAK;AAErB,eAAc,IAAI,mCAAmC,MAAM,EAAE,KAAK;EAChE,SAAS,QAAQ,WAAW;EAC5B,OAAO,QAAQ,oBAAoB;EACpC,CAAC,CAAC;AAEH,eAAc,MAAM,kCAAkC,OAAO,MAAM;AAEjE,MAAI,YAAY,MADG,EAAE,IAAI,MAAM,CAAC,YAAY,KAAK,EAC3B,SAAS,KAAK,YAClC,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO;GAAqC,EAAE,IAAI;AAE/E,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM,OAAO,QAAQ,mBAAmB;GAAE,CAAC;GAC/D;AAEF,eAAc,KAAK,+BAA+B,SAAS,OAAO,MAAM;AACtE,MAAI,CAAC,QAAQ,WAAW,CAAE,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO;GAA8B,EAAE,IAAI;EAEhG,MAAM,WAAW,YAAY,MADV,EAAE,IAAI,MAAM,CAAC,YAAY,KAAK,EACd,WAAW;AAC9C,MAAI,CAAC,SAAU,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO;GAAoB,EAAE,IAAI;AAC3E,MAAI;GACF,MAAM,UAAU,MAAM,yBAAyB,SAAS;GACxD,MAAM,WAAW,wBAAwB,KAAK,QAAQ,cAAc;AACpE,OAAI,CAAC,SAAU,QAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO;IAA+B,EAAE,IAAI;GACtF,MAAM,QAAQ,aAAa,SAAS;AACpC,UAAO,EAAE,KAAK;IACZ,IAAI;IACJ,SAAS;KACP,GAAG;KACH,QAAQ;KACR,UAAU;KACV,UAAU,MAAM;KAChB,aAAa,CAAC,oBAAoB,MAAM,QAAQ;KAChD,eAAA;KACD;IACF,CAAC;WACK,OAAO;AACd,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAAE,EAAE,IAAI;;GAElG;AAEF,eAAc,KAAK,4BAA4B,SAAS,OAAO,MAAM;AACnE,MAAI,CAAC,QAAQ,WAAW,CAAE,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO;GAA8B,EAAE,IAAI;EAChG,MAAM,OAAO,MAAM,EAAE,IAAI,MAAM,CAAC,YAAY,KAAK;EACjD,MAAM,WAAW,YAAY,MAAM,WAAW;EAC9C,MAAM,iBAAiB,YAAY,MAAM,iBAAiB;EAE1D,MAAM,SADkB,YAAY,MAAM,SACS,KAAK,8BACpD,8BACA;AACJ,MAAI,CAAC,YAAY,CAAC,eAChB,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO;GAAsC,EAAE,IAAI;AAEhF,MAAI;GACF,MAAM,MAAM,MAAM,QAAQ,SAAS;IAAE;IAAU;IAAQ;IAAgB,CAAC;AACxE,UAAO,EAAE,KAAK;IAAE,IAAI;IAAM;IAAK,EAAE,IAAI;WAC9B,OAAO;AACd,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAAE,EAAE,IAAI;;GAElG;AAEF,eAAc,IAAI,oCAAoC,MAAM;EAC1D,MAAM,MAAM,QAAQ,OAAO,EAAE,IAAI,MAAM,QAAQ,CAAC;AAChD,SAAO,MAAM,EAAE,KAAK;GAAE,IAAI;GAAM;GAAK,CAAC,GAAG,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO;GAAiB,EAAE,IAAI;GAC3F;AAEF,eAAc,KAAK,2CAA2C,MAAM;EAClE,MAAM,MAAM,QAAQ,UAAU,EAAE,IAAI,MAAM,QAAQ,CAAC;AACnD,SAAO,MAAM,EAAE,KAAK;GAAE,IAAI;GAAM;GAAK,CAAC,GAAG,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO;GAAiB,EAAE,IAAI;GAC3F;AAEF,eAAc,KAAK,0CAA0C,MAAM;EACjE,MAAM,MAAM,QAAQ,SAAS,EAAE,IAAI,MAAM,QAAQ,CAAC;AAClD,SAAO,MAAM,EAAE,KAAK;GAAE,IAAI;GAAM;GAAK,CAAC,GAAG,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO;GAAiB,EAAE,IAAI;GAC3F;AAEF,eAAc,KAAK,qEAAqE,MAAM;AAE5F,SADY,QAAQ,iBAAiB,EAAE,IAAI,MAAM,QAAQ,EAAE,EAAE,IAAI,MAAM,eAAe,CAC5E,GAAG,EAAE,KAAK,EAAE,IAAI,MAAM,CAAC,GAAG,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO;GAAwB,EAAE,IAAI;GAC7F"}
1
+ {"version":3,"file":"work-discovery.js","names":[],"sources":["../../../../../src/gateway/hono/routes/work-discovery.ts"],"sourcesContent":["import type { Hono } from 'hono';\n\nimport { getAgentDefaultModelRef } from '../../../config/schema.js';\nimport { isLocalModelBaseUrl } from '../../../providers/model-call.js';\nimport { resolveModel } from '../../../providers/index.js';\nimport { previewWorkDiscoveryRoot, WORK_DISCOVERY_SCAN_POLICY_VERSION } from '../../../work-discovery/probe.js';\nimport { WorkDiscoveryService } from '../../../work-discovery/service.js';\nimport type { WorkDiscoveryRecognitionDecision, WorkDiscoverySource } from '../../../work-discovery/types.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nconst services = new WeakMap<AuthenticatedRouteDeps['service'], WorkDiscoveryService>();\n\nfunction workDiscoveryService(deps: AuthenticatedRouteDeps): WorkDiscoveryService {\n const existing = services.get(deps.service);\n if (existing) return existing;\n const service = new WorkDiscoveryService({\n projects: deps.service.projects,\n sessions: deps.service.sessionIndexInstance,\n getConfig: () => deps.service.currentConfig,\n emit: (type, payload) => deps.service.emit(type, payload),\n });\n services.set(deps.service, service);\n return service;\n}\n\nfunction stringField(body: unknown, field: string): string {\n if (!body || typeof body !== 'object') return '';\n const value = (body as Record<string, unknown>)[field];\n return typeof value === 'string' ? value.trim() : '';\n}\n\nexport function registerWorkDiscoveryRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const service = workDiscoveryService(deps);\n const limited = deps.strictRateLimitMiddleware;\n\n authenticated.get('/api/onboarding/work-discovery', (c) => c.json({\n enabled: service.isEnabled(),\n state: service.getOnboardingState(),\n }));\n\n authenticated.patch('/api/onboarding/work-discovery', async (c) => {\n const body = await c.req.json().catch(() => null);\n if (stringField(body, 'status') !== 'dismissed') {\n return c.json({ ok: false, error: 'Only dismissed status is accepted' }, 400);\n }\n return c.json({ ok: true, state: service.dismissOnboarding() });\n });\n\n authenticated.post('/api/work-discovery/preview', limited, async (c) => {\n if (!service.isEnabled()) return c.json({ ok: false, error: 'Work discovery is disabled' }, 404);\n const body = await c.req.json().catch(() => null);\n const rootPath = stringField(body, 'rootPath');\n if (!rootPath) return c.json({ ok: false, error: 'Missing rootPath' }, 400);\n try {\n const preview = await previewWorkDiscoveryRoot(rootPath);\n const modelRef = getAgentDefaultModelRef(deps.service.currentConfig);\n if (!modelRef) return c.json({ ok: false, error: 'No default model configured' }, 409);\n const model = resolveModel(modelRef);\n return c.json({\n ok: true,\n preview: {\n ...preview,\n exists: true,\n readable: true,\n provider: model.provider,\n remoteModel: !isLocalModelBaseUrl(model.baseUrl),\n policyVersion: WORK_DISCOVERY_SCAN_POLICY_VERSION,\n },\n });\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 400);\n }\n });\n\n authenticated.post('/api/work-discovery/runs', limited, async (c) => {\n if (!service.isEnabled()) return c.json({ ok: false, error: 'Work discovery is disabled' }, 404);\n const body = await c.req.json().catch(() => null);\n const rootPath = stringField(body, 'rootPath');\n const idempotencyKey = stringField(body, 'idempotencyKey');\n const requestedSource = stringField(body, 'source');\n const source: WorkDiscoverySource = requestedSource === 'manual_selected_directory'\n ? 'manual_selected_directory'\n : 'onboarding_selected_directory';\n if (!rootPath || !idempotencyKey) {\n return c.json({ ok: false, error: 'Missing rootPath or idempotencyKey' }, 400);\n }\n try {\n const run = await service.startRun({ rootPath, source, idempotencyKey });\n return c.json({ ok: true, run }, 202);\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 400);\n }\n });\n\n authenticated.get('/api/work-discovery/runs/:runId', (c) => {\n const run = service.getRun(c.req.param('runId'));\n return run ? c.json({ ok: true, run }) : c.json({ ok: false, error: 'Run not found' }, 404);\n });\n\n authenticated.post('/api/work-discovery/runs/:runId/cancel', (c) => {\n const run = service.cancelRun(c.req.param('runId'));\n return run ? c.json({ ok: true, run }) : c.json({ ok: false, error: 'Run not found' }, 404);\n });\n\n authenticated.post('/api/work-discovery/runs/:runId/retry', (c) => {\n const run = service.retryRun(c.req.param('runId'));\n return run ? c.json({ ok: true, run }) : c.json({ ok: false, error: 'Run not found' }, 404);\n });\n\n authenticated.post('/api/work-discovery/runs/:runId/recognition-feedback', async (c) => {\n const body = await c.req.json().catch(() => null);\n const decision = stringField(body, 'decision');\n if (!['confirmed', 'corrected', 'different_goal', 'dismissed'].includes(decision)) {\n return c.json({ ok: false, error: 'Invalid recognition decision' }, 400);\n }\n try {\n const run = await service.submitRecognitionFeedback({\n runId: c.req.param('runId'),\n decision: decision as WorkDiscoveryRecognitionDecision,\n ...(stringField(body, 'correctedIntent') ? { correctedIntent: stringField(body, 'correctedIntent') } : {}),\n });\n return run ? c.json({ ok: true, run }) : c.json({ ok: false, error: 'Completed run not found' }, 404);\n } catch (error) {\n return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 400);\n }\n });\n\n authenticated.post('/api/work-discovery/runs/:runId/suggestions/:suggestionId/select', (c) => {\n const run = service.selectSuggestion(c.req.param('runId'), c.req.param('suggestionId'));\n return run ? c.json({ ok: true }) : c.json({ ok: false, error: 'Suggestion not found' }, 404);\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ CREATE TABLE IF NOT EXISTS work_discovery_feedback (
2
+ run_id TEXT PRIMARY KEY,
3
+ recognition_decision TEXT NOT NULL CHECK (
4
+ recognition_decision IN ('confirmed', 'corrected', 'different_goal', 'dismissed')
5
+ ),
6
+ corrected_intent TEXT,
7
+ created_at INTEGER NOT NULL,
8
+ updated_at INTEGER NOT NULL,
9
+ FOREIGN KEY (run_id) REFERENCES work_discovery_runs(id) ON DELETE CASCADE
10
+ );
11
+
12
+ CREATE INDEX IF NOT EXISTS idx_work_discovery_feedback_updated
13
+ ON work_discovery_feedback(updated_at DESC);
@@ -3,7 +3,7 @@ import type { ApplyMigrationsOptions } from './types.js';
3
3
  /** Baseline schema version applied from schema.sql on first open. */
4
4
  export declare const XOPC_DB_BASELINE_SCHEMA_VERSION = 11;
5
5
  /** Latest schema version this release supports (increment when adding migrations). */
6
- export declare const XOPC_DB_SCHEMA_VERSION = 45;
6
+ export declare const XOPC_DB_SCHEMA_VERSION = 46;
7
7
  export declare function resolveMigrationsDir(override?: string): string;
8
8
  /**
9
9
  * Apply sequential SQL migrations from (currentVersion + 1) through targetVersion.
@@ -12,7 +12,7 @@ const log = createLogger("Sqlite:Migrations");
12
12
  /** Baseline schema version applied from schema.sql on first open. */
13
13
  const XOPC_DB_BASELINE_SCHEMA_VERSION = 11;
14
14
  /** Latest schema version this release supports (increment when adding migrations). */
15
- const XOPC_DB_SCHEMA_VERSION = 45;
15
+ const XOPC_DB_SCHEMA_VERSION = 46;
16
16
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
17
17
  function resolveMigrationsDir(override) {
18
18
  if (override) return override;
@@ -46,7 +46,7 @@ function applySingleMigration(db, migration) {
46
46
  * Each step runs in its own transaction and updates schema_meta on success.
47
47
  */
48
48
  function applyPendingMigrations(db, options = {}) {
49
- const targetVersion = options.targetVersion ?? 45;
49
+ const targetVersion = options.targetVersion ?? 46;
50
50
  let currentVersion = readSchemaVersion(db);
51
51
  if (currentVersion > targetVersion) throw new DatabaseSchemaTooNewError(currentVersion, targetVersion);
52
52
  if (currentVersion >= targetVersion) return currentVersion;
@@ -62,7 +62,7 @@ function applyPendingMigrations(db, options = {}) {
62
62
  }
63
63
  /** Inspect schema version without mutating the database. */
64
64
  function inspectSchemaMigrationStatus(db, options = {}) {
65
- const appVersion = options.targetVersion ?? 45;
65
+ const appVersion = options.targetVersion ?? 46;
66
66
  const dbVersion = readSchemaVersion(db);
67
67
  if (dbVersion > appVersion) return {
68
68
  dbVersion,
@@ -1 +1 @@
1
- {"version":3,"file":"runner.js","names":[],"sources":["../../../../../src/storage/sqlite/migrations/runner.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { DatabaseSync } from 'node:sqlite';\n\nimport { createLogger } from '../../../utils/logger.js';\nimport { readSchemaVersion, setSchemaVersion } from '../schema-version.js';\nimport { discoverSqlMigrations } from './discover.js';\nimport {\n DatabaseSchemaMigrationGapError,\n DatabaseSchemaTooNewError,\n} from './errors.js';\nimport type { ApplyMigrationsOptions, SqlMigration } from './types.js';\n\nconst log = createLogger('Sqlite:Migrations');\n\n/** Baseline schema version applied from schema.sql on first open. */\nexport const XOPC_DB_BASELINE_SCHEMA_VERSION = 11;\n\n/** Latest schema version this release supports (increment when adding migrations). */\nexport const XOPC_DB_SCHEMA_VERSION = 45;\n\nconst MODULE_DIR = dirname(fileURLToPath(import.meta.url));\n\nexport function resolveMigrationsDir(override?: string): string {\n if (override) {\n return override;\n }\n // Packaged Electron gateway bundle: `out/server/index.js` + `out/server/migrations/`.\n const siblingDir = join(MODULE_DIR, 'migrations');\n if (existsSync(siblingDir)) {\n return siblingDir;\n }\n // Dev / dist: SQL files live next to `migrations/runner.js`.\n return MODULE_DIR;\n}\n\nfunction migrationByTarget(\n migrations: SqlMigration[],\n targetVersion: number,\n): SqlMigration | undefined {\n return migrations.find((migration) => migration.targetVersion === targetVersion);\n}\n\nfunction applySingleMigration(db: DatabaseSync, migration: SqlMigration): void {\n log.info({ targetVersion: migration.targetVersion, file: migration.filename }, 'Applying SQLite migration');\n db.exec('BEGIN IMMEDIATE');\n try {\n db.exec(migration.sql);\n setSchemaVersion(db, migration.targetVersion);\n db.exec('COMMIT');\n } catch (error) {\n try {\n db.exec('ROLLBACK');\n } catch {\n /* preserve original error */\n }\n const em = error instanceof Error ? error.message : String(error);\n throw new Error(\n `SQLite migration to v${migration.targetVersion} (${migration.filename}) failed: ${em}`,\n { cause: error },\n );\n }\n}\n\n/**\n * Apply sequential SQL migrations from (currentVersion + 1) through targetVersion.\n * Each step runs in its own transaction and updates schema_meta on success.\n */\nexport function applyPendingMigrations(\n db: DatabaseSync,\n options: ApplyMigrationsOptions = {},\n): number {\n const targetVersion = options.targetVersion ?? XOPC_DB_SCHEMA_VERSION;\n let currentVersion = readSchemaVersion(db);\n\n if (currentVersion > targetVersion) {\n throw new DatabaseSchemaTooNewError(currentVersion, targetVersion);\n }\n\n if (currentVersion >= targetVersion) {\n return currentVersion;\n }\n\n const migrations = discoverSqlMigrations(resolveMigrationsDir(options.migrationsDir));\n\n while (currentVersion < targetVersion) {\n const nextVersion = currentVersion + 1;\n const migration = migrationByTarget(migrations, nextVersion);\n if (!migration) {\n throw new DatabaseSchemaMigrationGapError(currentVersion, targetVersion, nextVersion);\n }\n applySingleMigration(db, migration);\n currentVersion = nextVersion;\n }\n\n return currentVersion;\n}\n\nexport type SchemaMigrationStatus = {\n dbVersion: number;\n appVersion: number;\n pendingVersions: number[];\n isTooNew: boolean;\n hasMigrationGap: boolean;\n missingVersion: number | null;\n};\n\n/** Inspect schema version without mutating the database. */\nexport function inspectSchemaMigrationStatus(\n db: DatabaseSync,\n options: Pick<ApplyMigrationsOptions, 'migrationsDir' | 'targetVersion'> = {},\n): SchemaMigrationStatus {\n const appVersion = options.targetVersion ?? XOPC_DB_SCHEMA_VERSION;\n const dbVersion = readSchemaVersion(db);\n const isTooNew = dbVersion > appVersion;\n\n if (isTooNew) {\n return {\n dbVersion,\n appVersion,\n pendingVersions: [],\n isTooNew: true,\n hasMigrationGap: false,\n missingVersion: null,\n };\n }\n\n const migrations = discoverSqlMigrations(resolveMigrationsDir(options.migrationsDir));\n const pendingVersions: number[] = [];\n let hasMigrationGap = false;\n let missingVersion: number | null = null;\n\n for (let version = dbVersion + 1; version <= appVersion; version++) {\n if (migrationByTarget(migrations, version)) {\n pendingVersions.push(version);\n } else {\n hasMigrationGap = true;\n missingVersion = version;\n break;\n }\n }\n\n return {\n dbVersion,\n appVersion,\n pendingVersions,\n isTooNew: false,\n hasMigrationGap,\n missingVersion,\n };\n}\n"],"mappings":";;;;;;;;;aAMwD;AASxD,MAAM,MAAM,aAAa,oBAAoB;;AAG7C,MAAa,kCAAkC;;AAG/C,MAAa,yBAAyB;AAEtC,MAAM,aAAa,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAE1D,SAAgB,qBAAqB,UAA2B;AAC9D,KAAI,SACF,QAAO;CAGT,MAAM,aAAa,KAAK,YAAY,aAAa;AACjD,KAAI,WAAW,WAAW,CACxB,QAAO;AAGT,QAAO;;AAGT,SAAS,kBACP,YACA,eAC0B;AAC1B,QAAO,WAAW,MAAM,cAAc,UAAU,kBAAkB,cAAc;;AAGlF,SAAS,qBAAqB,IAAkB,WAA+B;AAC7E,KAAI,KAAK;EAAE,eAAe,UAAU;EAAe,MAAM,UAAU;EAAU,EAAE,4BAA4B;AAC3G,IAAG,KAAK,kBAAkB;AAC1B,KAAI;AACF,KAAG,KAAK,UAAU,IAAI;AACtB,mBAAiB,IAAI,UAAU,cAAc;AAC7C,KAAG,KAAK,SAAS;UACV,OAAO;AACd,MAAI;AACF,MAAG,KAAK,WAAW;UACb;EAGR,MAAM,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACjE,QAAM,IAAI,MACR,wBAAwB,UAAU,cAAc,IAAI,UAAU,SAAS,YAAY,MACnF,EAAE,OAAO,OAAO,CACjB;;;;;;;AAQL,SAAgB,uBACd,IACA,UAAkC,EAAE,EAC5B;CACR,MAAM,gBAAgB,QAAQ,iBAAA;CAC9B,IAAI,iBAAiB,kBAAkB,GAAG;AAE1C,KAAI,iBAAiB,cACnB,OAAM,IAAI,0BAA0B,gBAAgB,cAAc;AAGpE,KAAI,kBAAkB,cACpB,QAAO;CAGT,MAAM,aAAa,sBAAsB,qBAAqB,QAAQ,cAAc,CAAC;AAErF,QAAO,iBAAiB,eAAe;EACrC,MAAM,cAAc,iBAAiB;EACrC,MAAM,YAAY,kBAAkB,YAAY,YAAY;AAC5D,MAAI,CAAC,UACH,OAAM,IAAI,gCAAgC,gBAAgB,eAAe,YAAY;AAEvF,uBAAqB,IAAI,UAAU;AACnC,mBAAiB;;AAGnB,QAAO;;;AAaT,SAAgB,6BACd,IACA,UAA2E,EAAE,EACtD;CACvB,MAAM,aAAa,QAAQ,iBAAA;CAC3B,MAAM,YAAY,kBAAkB,GAAG;AAGvC,KAFiB,YAAY,WAG3B,QAAO;EACL;EACA;EACA,iBAAiB,EAAE;EACnB,UAAU;EACV,iBAAiB;EACjB,gBAAgB;EACjB;CAGH,MAAM,aAAa,sBAAsB,qBAAqB,QAAQ,cAAc,CAAC;CACrF,MAAM,kBAA4B,EAAE;CACpC,IAAI,kBAAkB;CACtB,IAAI,iBAAgC;AAEpC,MAAK,IAAI,UAAU,YAAY,GAAG,WAAW,YAAY,UACvD,KAAI,kBAAkB,YAAY,QAAQ,CACxC,iBAAgB,KAAK,QAAQ;MACxB;AACL,oBAAkB;AAClB,mBAAiB;AACjB;;AAIJ,QAAO;EACL;EACA;EACA;EACA,UAAU;EACV;EACA;EACD"}
1
+ {"version":3,"file":"runner.js","names":[],"sources":["../../../../../src/storage/sqlite/migrations/runner.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { DatabaseSync } from 'node:sqlite';\n\nimport { createLogger } from '../../../utils/logger.js';\nimport { readSchemaVersion, setSchemaVersion } from '../schema-version.js';\nimport { discoverSqlMigrations } from './discover.js';\nimport {\n DatabaseSchemaMigrationGapError,\n DatabaseSchemaTooNewError,\n} from './errors.js';\nimport type { ApplyMigrationsOptions, SqlMigration } from './types.js';\n\nconst log = createLogger('Sqlite:Migrations');\n\n/** Baseline schema version applied from schema.sql on first open. */\nexport const XOPC_DB_BASELINE_SCHEMA_VERSION = 11;\n\n/** Latest schema version this release supports (increment when adding migrations). */\nexport const XOPC_DB_SCHEMA_VERSION = 46;\n\nconst MODULE_DIR = dirname(fileURLToPath(import.meta.url));\n\nexport function resolveMigrationsDir(override?: string): string {\n if (override) {\n return override;\n }\n // Packaged Electron gateway bundle: `out/server/index.js` + `out/server/migrations/`.\n const siblingDir = join(MODULE_DIR, 'migrations');\n if (existsSync(siblingDir)) {\n return siblingDir;\n }\n // Dev / dist: SQL files live next to `migrations/runner.js`.\n return MODULE_DIR;\n}\n\nfunction migrationByTarget(\n migrations: SqlMigration[],\n targetVersion: number,\n): SqlMigration | undefined {\n return migrations.find((migration) => migration.targetVersion === targetVersion);\n}\n\nfunction applySingleMigration(db: DatabaseSync, migration: SqlMigration): void {\n log.info({ targetVersion: migration.targetVersion, file: migration.filename }, 'Applying SQLite migration');\n db.exec('BEGIN IMMEDIATE');\n try {\n db.exec(migration.sql);\n setSchemaVersion(db, migration.targetVersion);\n db.exec('COMMIT');\n } catch (error) {\n try {\n db.exec('ROLLBACK');\n } catch {\n /* preserve original error */\n }\n const em = error instanceof Error ? error.message : String(error);\n throw new Error(\n `SQLite migration to v${migration.targetVersion} (${migration.filename}) failed: ${em}`,\n { cause: error },\n );\n }\n}\n\n/**\n * Apply sequential SQL migrations from (currentVersion + 1) through targetVersion.\n * Each step runs in its own transaction and updates schema_meta on success.\n */\nexport function applyPendingMigrations(\n db: DatabaseSync,\n options: ApplyMigrationsOptions = {},\n): number {\n const targetVersion = options.targetVersion ?? XOPC_DB_SCHEMA_VERSION;\n let currentVersion = readSchemaVersion(db);\n\n if (currentVersion > targetVersion) {\n throw new DatabaseSchemaTooNewError(currentVersion, targetVersion);\n }\n\n if (currentVersion >= targetVersion) {\n return currentVersion;\n }\n\n const migrations = discoverSqlMigrations(resolveMigrationsDir(options.migrationsDir));\n\n while (currentVersion < targetVersion) {\n const nextVersion = currentVersion + 1;\n const migration = migrationByTarget(migrations, nextVersion);\n if (!migration) {\n throw new DatabaseSchemaMigrationGapError(currentVersion, targetVersion, nextVersion);\n }\n applySingleMigration(db, migration);\n currentVersion = nextVersion;\n }\n\n return currentVersion;\n}\n\nexport type SchemaMigrationStatus = {\n dbVersion: number;\n appVersion: number;\n pendingVersions: number[];\n isTooNew: boolean;\n hasMigrationGap: boolean;\n missingVersion: number | null;\n};\n\n/** Inspect schema version without mutating the database. */\nexport function inspectSchemaMigrationStatus(\n db: DatabaseSync,\n options: Pick<ApplyMigrationsOptions, 'migrationsDir' | 'targetVersion'> = {},\n): SchemaMigrationStatus {\n const appVersion = options.targetVersion ?? XOPC_DB_SCHEMA_VERSION;\n const dbVersion = readSchemaVersion(db);\n const isTooNew = dbVersion > appVersion;\n\n if (isTooNew) {\n return {\n dbVersion,\n appVersion,\n pendingVersions: [],\n isTooNew: true,\n hasMigrationGap: false,\n missingVersion: null,\n };\n }\n\n const migrations = discoverSqlMigrations(resolveMigrationsDir(options.migrationsDir));\n const pendingVersions: number[] = [];\n let hasMigrationGap = false;\n let missingVersion: number | null = null;\n\n for (let version = dbVersion + 1; version <= appVersion; version++) {\n if (migrationByTarget(migrations, version)) {\n pendingVersions.push(version);\n } else {\n hasMigrationGap = true;\n missingVersion = version;\n break;\n }\n }\n\n return {\n dbVersion,\n appVersion,\n pendingVersions,\n isTooNew: false,\n hasMigrationGap,\n missingVersion,\n };\n}\n"],"mappings":";;;;;;;;;aAMwD;AASxD,MAAM,MAAM,aAAa,oBAAoB;;AAG7C,MAAa,kCAAkC;;AAG/C,MAAa,yBAAyB;AAEtC,MAAM,aAAa,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAE1D,SAAgB,qBAAqB,UAA2B;AAC9D,KAAI,SACF,QAAO;CAGT,MAAM,aAAa,KAAK,YAAY,aAAa;AACjD,KAAI,WAAW,WAAW,CACxB,QAAO;AAGT,QAAO;;AAGT,SAAS,kBACP,YACA,eAC0B;AAC1B,QAAO,WAAW,MAAM,cAAc,UAAU,kBAAkB,cAAc;;AAGlF,SAAS,qBAAqB,IAAkB,WAA+B;AAC7E,KAAI,KAAK;EAAE,eAAe,UAAU;EAAe,MAAM,UAAU;EAAU,EAAE,4BAA4B;AAC3G,IAAG,KAAK,kBAAkB;AAC1B,KAAI;AACF,KAAG,KAAK,UAAU,IAAI;AACtB,mBAAiB,IAAI,UAAU,cAAc;AAC7C,KAAG,KAAK,SAAS;UACV,OAAO;AACd,MAAI;AACF,MAAG,KAAK,WAAW;UACb;EAGR,MAAM,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACjE,QAAM,IAAI,MACR,wBAAwB,UAAU,cAAc,IAAI,UAAU,SAAS,YAAY,MACnF,EAAE,OAAO,OAAO,CACjB;;;;;;;AAQL,SAAgB,uBACd,IACA,UAAkC,EAAE,EAC5B;CACR,MAAM,gBAAgB,QAAQ,iBAAA;CAC9B,IAAI,iBAAiB,kBAAkB,GAAG;AAE1C,KAAI,iBAAiB,cACnB,OAAM,IAAI,0BAA0B,gBAAgB,cAAc;AAGpE,KAAI,kBAAkB,cACpB,QAAO;CAGT,MAAM,aAAa,sBAAsB,qBAAqB,QAAQ,cAAc,CAAC;AAErF,QAAO,iBAAiB,eAAe;EACrC,MAAM,cAAc,iBAAiB;EACrC,MAAM,YAAY,kBAAkB,YAAY,YAAY;AAC5D,MAAI,CAAC,UACH,OAAM,IAAI,gCAAgC,gBAAgB,eAAe,YAAY;AAEvF,uBAAqB,IAAI,UAAU;AACnC,mBAAiB;;AAGnB,QAAO;;;AAaT,SAAgB,6BACd,IACA,UAA2E,EAAE,EACtD;CACvB,MAAM,aAAa,QAAQ,iBAAA;CAC3B,MAAM,YAAY,kBAAkB,GAAG;AAGvC,KAFiB,YAAY,WAG3B,QAAO;EACL;EACA;EACA,iBAAiB,EAAE;EACnB,UAAU;EACV,iBAAiB;EACjB,gBAAgB;EACjB;CAGH,MAAM,aAAa,sBAAsB,qBAAqB,QAAQ,cAAc,CAAC;CACrF,MAAM,kBAA4B,EAAE;CACpC,IAAI,kBAAkB;CACtB,IAAI,iBAAgC;AAEpC,MAAK,IAAI,UAAU,YAAY,GAAG,WAAW,YAAY,UACvD,KAAI,kBAAkB,YAAY,QAAQ,CACxC,iBAAgB,KAAK,QAAQ;MACxB;AACL,oBAAkB;AAClB,mBAAiB;AACjB;;AAIJ,QAAO;EACL;EACA;EACA;EACA,UAAU;EACV;EACA;EACD"}
@@ -19,7 +19,7 @@ const log = createLogger('Sqlite:Migrations');
19
19
  export const XOPC_DB_BASELINE_SCHEMA_VERSION = 11;
20
20
 
21
21
  /** Latest schema version this release supports (increment when adding migrations). */
22
- export const XOPC_DB_SCHEMA_VERSION = 45;
22
+ export const XOPC_DB_SCHEMA_VERSION = 46;
23
23
 
24
24
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
25
25
 
@@ -31,7 +31,7 @@ function ensureXopcDatabaseSchema(db) {
31
31
  bootstrapFreshDatabase(db);
32
32
  currentVersion = 11;
33
33
  }
34
- applyPendingMigrations(db, { targetVersion: 45 });
34
+ applyPendingMigrations(db, { targetVersion: 46 });
35
35
  }
36
36
  //#endregion
37
37
  export { SCHEMA_META_SCHEMA_VERSION_KEY, XOPC_DB_BASELINE_SCHEMA_VERSION, XOPC_DB_SCHEMA_VERSION, ensureSchemaMetaTable, ensureXopcDatabaseSchema, readSchemaVersion, readSchemaVersionForTest, setSchemaVersion };
@@ -37,6 +37,10 @@ function validateSuggestion(value, allowedPaths) {
37
37
  if (typeof item.rationale !== "string" || !item.rationale.trim()) return null;
38
38
  if (typeof item.actionPrompt !== "string" || !item.actionPrompt.trim()) return null;
39
39
  const confidence = item.confidence === "high" || item.confidence === "medium" || item.confidence === "low" ? item.confidence : "medium";
40
+ const actionType = item.actionType === "summarize_recent_work" || item.actionType === "inspect_related_tests" || item.actionType === "plan_next_step" ? item.actionType : "plan_next_step";
41
+ const risk = item.risk === "command" || item.risk === "file_write" ? item.risk : "analysis";
42
+ const estimatedMinutes = typeof item.estimatedMinutes === "number" && Number.isFinite(item.estimatedMinutes) ? Math.max(1, Math.min(30, Math.round(item.estimatedMinutes))) : 3;
43
+ const expectedOutcome = typeof item.expectedOutcome === "string" && item.expectedOutcome.trim() ? item.expectedOutcome.trim().slice(0, 500) : item.title.trim().slice(0, 120);
40
44
  const evidence = Array.isArray(item.evidence) ? item.evidence.flatMap((raw) => {
41
45
  if (!raw || typeof raw !== "object") return [];
42
46
  const candidate = raw;
@@ -50,13 +54,23 @@ function validateSuggestion(value, allowedPaths) {
50
54
  if (evidence.length === 0) return null;
51
55
  return {
52
56
  id: randomUUID(),
57
+ actionType,
53
58
  title: item.title.trim().slice(0, 120),
54
59
  rationale: item.rationale.trim().slice(0, 600),
55
60
  evidence,
56
61
  actionPrompt: item.actionPrompt.trim().slice(0, 2e3),
57
- confidence
62
+ confidence,
63
+ expectedOutcome,
64
+ estimatedMinutes,
65
+ risk,
66
+ verification: strings(item.verification, 4).map((entry) => entry.slice(0, 300))
58
67
  };
59
68
  }
69
+ function suggestionScore(suggestion) {
70
+ const confidence = suggestion.confidence === "high" ? 3 : suggestion.confidence === "medium" ? 2 : 1;
71
+ const safety = suggestion.risk === "analysis" ? 3 : suggestion.risk === "command" ? 2 : 1;
72
+ return confidence * 4 + safety * 2 - Math.min(suggestion.estimatedMinutes, 10) / 10;
73
+ }
60
74
  function snapshotForModel(snapshot) {
61
75
  let remaining = MAX_MODEL_CONTEXT_CHARS;
62
76
  return {
@@ -104,7 +118,10 @@ async function analyzeWorkContext(input) {
104
118
  "Analyze only the supplied bounded snapshot. Never claim that you ran commands, tests, or inspected anything absent from it.",
105
119
  "Return only one JSON object with projectSummary, currentState, uncertainties, suggestions, lowConfidence, and contextQuestion.",
106
120
  "For a normal result, suggestions must contain exactly 3 materially different next steps.",
107
- "Each suggestion must include title, rationale, evidence, actionPrompt, and confidence (high, medium, or low).",
121
+ "Each suggestion must include actionType, title, rationale, evidence, actionPrompt, confidence, expectedOutcome, estimatedMinutes, risk, and verification.",
122
+ "actionType must be summarize_recent_work, inspect_related_tests, or plan_next_step.",
123
+ "risk must be analysis, command, or file_write. estimatedMinutes is an integer from 1 to 30.",
124
+ "verification is a short array describing how the user can tell the outcome is real.",
108
125
  "Each evidence item contains an optional exact relative path from the snapshot and one concrete observation.",
109
126
  "actionPrompt asks the assistant to investigate or continue the step; it does not silently authorize file changes.",
110
127
  "If the current objective is unclear or fewer than three credible suggestions exist, set lowConfidence=true, return suggestions=[], and ask one concise contextQuestion.",
@@ -133,6 +150,7 @@ async function analyzeWorkContext(input) {
133
150
  modelRef,
134
151
  result: lowConfidenceResult(input.snapshot)
135
152
  };
153
+ const primarySuggestion = [...suggestions].sort((a, b) => suggestionScore(b) - suggestionScore(a))[0];
136
154
  const projectSummary = typeof parsed.projectSummary === "string" ? parsed.projectSummary.trim() : "";
137
155
  const currentState = typeof parsed.currentState === "string" ? parsed.currentState.trim() : "";
138
156
  if (!projectSummary || !currentState) throw new Error("Analysis result is missing its summary");
@@ -142,7 +160,8 @@ async function analyzeWorkContext(input) {
142
160
  projectSummary: projectSummary.slice(0, 1200),
143
161
  currentState: currentState.slice(0, 1200),
144
162
  uncertainties: strings(parsed.uncertainties, 6).map((value) => value.slice(0, 500)),
145
- suggestions
163
+ suggestions,
164
+ ...primarySuggestion ? { primarySuggestionId: primarySuggestion.id } : {}
146
165
  }
147
166
  };
148
167
  }
@@ -160,7 +179,7 @@ function workDiscoveryResultMarkdown(result) {
160
179
  }
161
180
  lines.push("", "## Suggested next steps");
162
181
  for (const [index, suggestion] of result.suggestions.entries()) {
163
- lines.push("", `### ${index + 1}. ${suggestion.title}`, "", suggestion.rationale, "");
182
+ lines.push("", `### ${index + 1}. ${suggestion.title}`, "", suggestion.rationale, "", `Expected outcome: ${suggestion.expectedOutcome}`, "");
164
183
  for (const evidence of suggestion.evidence) lines.push(`- ${evidence.path ? `\`${evidence.path}\`: ` : ""}${evidence.observation}`);
165
184
  }
166
185
  return lines.join("\n");
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.js","names":[],"sources":["../../../src/work-discovery/analyzer.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type { UserMessage } from '@earendil-works/pi-ai/compat';\n\nimport type { Config } from '../config/schema.js';\nimport { getAgentDefaultModelRef } from '../config/schema.js';\nimport { completeWithResolvedCredentials } from '../providers/model-call.js';\nimport { resolveModel } from '../providers/index.js';\n\nimport type {\n WorkContextSnapshot,\n WorkDiscoveryResult,\n WorkDiscoverySuggestion,\n} from './types.js';\n\nconst MAX_MODEL_CONTEXT_CHARS = 120_000;\n\nfunction extractText(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return content.map((part) => {\n if (!part || typeof part !== 'object') return '';\n const value = part as { type?: unknown; text?: unknown };\n return value.type === 'text' && typeof value.text === 'string' ? value.text : '';\n }).join('');\n}\n\nfunction parseJson(raw: string): Record<string, unknown> | null {\n const fenced = /```(?:json)?\\s*([\\s\\S]*?)```/i.exec(raw)?.[1];\n const start = raw.indexOf('{');\n const end = raw.lastIndexOf('}');\n const candidate = fenced ?? (start >= 0 && end > start ? raw.slice(start, end + 1) : raw);\n try {\n const value = JSON.parse(candidate) as unknown;\n return value && typeof value === 'object' && !Array.isArray(value)\n ? value as Record<string, unknown>\n : null;\n } catch {\n return null;\n }\n}\n\nfunction strings(value: unknown, max: number): string[] {\n return Array.isArray(value)\n ? value.filter((item): item is string => typeof item === 'string' && item.trim().length > 0)\n .slice(0, max)\n .map((item) => item.trim())\n : [];\n}\n\nfunction validateSuggestion(value: unknown, allowedPaths: Set<string>): WorkDiscoverySuggestion | null {\n if (!value || typeof value !== 'object') return null;\n const item = value as Record<string, unknown>;\n if (typeof item.title !== 'string' || !item.title.trim()) return null;\n if (typeof item.rationale !== 'string' || !item.rationale.trim()) return null;\n if (typeof item.actionPrompt !== 'string' || !item.actionPrompt.trim()) return null;\n const confidence = item.confidence === 'high' || item.confidence === 'medium' || item.confidence === 'low'\n ? item.confidence\n : 'medium';\n const evidence = Array.isArray(item.evidence)\n ? item.evidence.flatMap((raw) => {\n if (!raw || typeof raw !== 'object') return [];\n const candidate = raw as Record<string, unknown>;\n if (typeof candidate.observation !== 'string' || !candidate.observation.trim()) return [];\n const path = typeof candidate.path === 'string' && allowedPaths.has(candidate.path)\n ? candidate.path\n : undefined;\n return [{ ...(path ? { path } : {}), observation: candidate.observation.trim().slice(0, 500) }];\n }).slice(0, 4)\n : [];\n if (evidence.length === 0) return null;\n return {\n id: randomUUID(),\n title: item.title.trim().slice(0, 120),\n rationale: item.rationale.trim().slice(0, 600),\n evidence,\n actionPrompt: item.actionPrompt.trim().slice(0, 2_000),\n confidence,\n };\n}\n\nfunction snapshotForModel(snapshot: WorkContextSnapshot): WorkContextSnapshot {\n let remaining = MAX_MODEL_CONTEXT_CHARS;\n return {\n ...snapshot,\n structure: {\n ...snapshot.structure,\n sampledPaths: snapshot.structure.sampledPaths.slice(0, 200),\n metadataOnlyFiles: snapshot.structure.metadataOnlyFiles.slice(0, 200),\n },\n documents: snapshot.documents.flatMap((document) => {\n if (remaining <= 0) return [];\n const excerpt = document.excerpt.slice(0, remaining);\n remaining -= excerpt.length;\n return [{ ...document, excerpt, truncated: document.truncated || excerpt.length < document.excerpt.length }];\n }),\n };\n}\n\nfunction lowConfidenceResult(snapshot: WorkContextSnapshot, question?: string): WorkDiscoveryResult {\n return {\n projectSummary: snapshot.root.projectKind === 'coding'\n ? `${snapshot.root.displayName} appears to be a software project.`\n : `${snapshot.root.displayName} is a local work folder.`,\n currentState: snapshot.git?.changedPaths.length\n ? `${snapshot.git.changedPaths.length} changed paths were detected, but there was not enough reliable context to recommend work.`\n : 'There was not enough recent, readable context to determine the current objective.',\n uncertainties: ['The current objective is not explicit in the selected files.'],\n suggestions: [],\n lowConfidence: true,\n contextQuestion: question ?? 'What are you trying to move forward in this folder?',\n };\n}\n\nexport async function analyzeWorkContext(input: {\n config: Config;\n snapshot: WorkContextSnapshot;\n signal?: AbortSignal;\n}): Promise<{ modelRef: string; result: WorkDiscoveryResult }> {\n const modelRef = getAgentDefaultModelRef(input.config);\n if (!modelRef) throw new Error('No default model configured');\n if (input.snapshot.documents.length === 0 && !input.snapshot.git?.changedPaths.length) {\n return { modelRef, result: lowConfidenceResult(input.snapshot) };\n }\n\n const model = resolveModel(modelRef);\n const compactSnapshot = snapshotForModel(input.snapshot);\n const prompt = [\n 'You help a user resume real work in one explicitly selected local folder.',\n 'Analyze only the supplied bounded snapshot. Never claim that you ran commands, tests, or inspected anything absent from it.',\n 'Return only one JSON object with projectSummary, currentState, uncertainties, suggestions, lowConfidence, and contextQuestion.',\n 'For a normal result, suggestions must contain exactly 3 materially different next steps.',\n 'Each suggestion must include title, rationale, evidence, actionPrompt, and confidence (high, medium, or low).',\n 'Each evidence item contains an optional exact relative path from the snapshot and one concrete observation.',\n 'actionPrompt asks the assistant to investigate or continue the step; it does not silently authorize file changes.',\n 'If the current objective is unclear or fewer than three credible suggestions exist, set lowConfidence=true, return suggestions=[], and ask one concise contextQuestion.',\n 'Use Simplified Chinese for every user-facing string when the visible snapshot documents are mainly Chinese; otherwise use English.',\n '',\n JSON.stringify(compactSnapshot),\n ].join('\\n');\n const message: UserMessage = { role: 'user', content: prompt, timestamp: Date.now() };\n const response = await completeWithResolvedCredentials(model, { messages: [message] }, {\n maxTokens: 2_500,\n temperature: 0.1,\n signal: input.signal,\n });\n const parsed = parseJson(extractText(response.content));\n if (!parsed) throw new Error('Analysis did not return valid JSON');\n\n const lowConfidence = parsed.lowConfidence === true;\n if (lowConfidence) {\n return {\n modelRef,\n result: lowConfidenceResult(\n input.snapshot,\n typeof parsed.contextQuestion === 'string' ? parsed.contextQuestion.trim().slice(0, 500) : undefined,\n ),\n };\n }\n\n const allowedPaths = new Set([\n ...input.snapshot.structure.sampledPaths,\n ...input.snapshot.documents.map((document) => document.relativePath),\n ...(input.snapshot.git?.changedPaths ?? []),\n ]);\n const suggestions = Array.isArray(parsed.suggestions)\n ? parsed.suggestions.map((value) => validateSuggestion(value, allowedPaths)).filter((value): value is WorkDiscoverySuggestion => Boolean(value))\n : [];\n if (suggestions.length !== 3) return { modelRef, result: lowConfidenceResult(input.snapshot) };\n const projectSummary = typeof parsed.projectSummary === 'string' ? parsed.projectSummary.trim() : '';\n const currentState = typeof parsed.currentState === 'string' ? parsed.currentState.trim() : '';\n if (!projectSummary || !currentState) throw new Error('Analysis result is missing its summary');\n return {\n modelRef,\n result: {\n projectSummary: projectSummary.slice(0, 1_200),\n currentState: currentState.slice(0, 1_200),\n uncertainties: strings(parsed.uncertainties, 6).map((value) => value.slice(0, 500)),\n suggestions,\n },\n };\n}\n\nexport function workDiscoveryResultMarkdown(result: WorkDiscoveryResult): string {\n const lines = [`## What I found`, '', result.projectSummary, '', result.currentState];\n if (result.lowConfidence) {\n if (result.contextQuestion) lines.push('', `**${result.contextQuestion}**`);\n return lines.join('\\n');\n }\n lines.push('', '## Suggested next steps');\n for (const [index, suggestion] of result.suggestions.entries()) {\n lines.push('', `### ${index + 1}. ${suggestion.title}`, '', suggestion.rationale, '');\n for (const evidence of suggestion.evidence) {\n lines.push(`- ${evidence.path ? `\\`${evidence.path}\\`: ` : ''}${evidence.observation}`);\n }\n }\n return lines.join('\\n');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"analyzer.js","names":[],"sources":["../../../src/work-discovery/analyzer.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type { UserMessage } from '@earendil-works/pi-ai/compat';\n\nimport type { Config } from '../config/schema.js';\nimport { getAgentDefaultModelRef } from '../config/schema.js';\nimport { completeWithResolvedCredentials } from '../providers/model-call.js';\nimport { resolveModel } from '../providers/index.js';\n\nimport type {\n WorkContextSnapshot,\n WorkDiscoveryResult,\n WorkDiscoverySuggestion,\n} from './types.js';\n\nconst MAX_MODEL_CONTEXT_CHARS = 120_000;\n\nfunction extractText(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return content.map((part) => {\n if (!part || typeof part !== 'object') return '';\n const value = part as { type?: unknown; text?: unknown };\n return value.type === 'text' && typeof value.text === 'string' ? value.text : '';\n }).join('');\n}\n\nfunction parseJson(raw: string): Record<string, unknown> | null {\n const fenced = /```(?:json)?\\s*([\\s\\S]*?)```/i.exec(raw)?.[1];\n const start = raw.indexOf('{');\n const end = raw.lastIndexOf('}');\n const candidate = fenced ?? (start >= 0 && end > start ? raw.slice(start, end + 1) : raw);\n try {\n const value = JSON.parse(candidate) as unknown;\n return value && typeof value === 'object' && !Array.isArray(value)\n ? value as Record<string, unknown>\n : null;\n } catch {\n return null;\n }\n}\n\nfunction strings(value: unknown, max: number): string[] {\n return Array.isArray(value)\n ? value.filter((item): item is string => typeof item === 'string' && item.trim().length > 0)\n .slice(0, max)\n .map((item) => item.trim())\n : [];\n}\n\nfunction validateSuggestion(value: unknown, allowedPaths: Set<string>): WorkDiscoverySuggestion | null {\n if (!value || typeof value !== 'object') return null;\n const item = value as Record<string, unknown>;\n if (typeof item.title !== 'string' || !item.title.trim()) return null;\n if (typeof item.rationale !== 'string' || !item.rationale.trim()) return null;\n if (typeof item.actionPrompt !== 'string' || !item.actionPrompt.trim()) return null;\n const confidence = item.confidence === 'high' || item.confidence === 'medium' || item.confidence === 'low'\n ? item.confidence\n : 'medium';\n const actionType = item.actionType === 'summarize_recent_work'\n || item.actionType === 'inspect_related_tests'\n || item.actionType === 'plan_next_step'\n ? item.actionType\n : 'plan_next_step';\n const risk = item.risk === 'command' || item.risk === 'file_write' ? item.risk : 'analysis';\n const estimatedMinutes = typeof item.estimatedMinutes === 'number' && Number.isFinite(item.estimatedMinutes)\n ? Math.max(1, Math.min(30, Math.round(item.estimatedMinutes)))\n : 3;\n const expectedOutcome = typeof item.expectedOutcome === 'string' && item.expectedOutcome.trim()\n ? item.expectedOutcome.trim().slice(0, 500)\n : item.title.trim().slice(0, 120);\n const evidence = Array.isArray(item.evidence)\n ? item.evidence.flatMap((raw) => {\n if (!raw || typeof raw !== 'object') return [];\n const candidate = raw as Record<string, unknown>;\n if (typeof candidate.observation !== 'string' || !candidate.observation.trim()) return [];\n const path = typeof candidate.path === 'string' && allowedPaths.has(candidate.path)\n ? candidate.path\n : undefined;\n return [{ ...(path ? { path } : {}), observation: candidate.observation.trim().slice(0, 500) }];\n }).slice(0, 4)\n : [];\n if (evidence.length === 0) return null;\n return {\n id: randomUUID(),\n actionType,\n title: item.title.trim().slice(0, 120),\n rationale: item.rationale.trim().slice(0, 600),\n evidence,\n actionPrompt: item.actionPrompt.trim().slice(0, 2_000),\n confidence,\n expectedOutcome,\n estimatedMinutes,\n risk,\n verification: strings(item.verification, 4).map((entry) => entry.slice(0, 300)),\n };\n}\n\nfunction suggestionScore(suggestion: WorkDiscoverySuggestion): number {\n const confidence = suggestion.confidence === 'high' ? 3 : suggestion.confidence === 'medium' ? 2 : 1;\n const safety = suggestion.risk === 'analysis' ? 3 : suggestion.risk === 'command' ? 2 : 1;\n return confidence * 4 + safety * 2 - Math.min(suggestion.estimatedMinutes, 10) / 10;\n}\n\nfunction snapshotForModel(snapshot: WorkContextSnapshot): WorkContextSnapshot {\n let remaining = MAX_MODEL_CONTEXT_CHARS;\n return {\n ...snapshot,\n structure: {\n ...snapshot.structure,\n sampledPaths: snapshot.structure.sampledPaths.slice(0, 200),\n metadataOnlyFiles: snapshot.structure.metadataOnlyFiles.slice(0, 200),\n },\n documents: snapshot.documents.flatMap((document) => {\n if (remaining <= 0) return [];\n const excerpt = document.excerpt.slice(0, remaining);\n remaining -= excerpt.length;\n return [{ ...document, excerpt, truncated: document.truncated || excerpt.length < document.excerpt.length }];\n }),\n };\n}\n\nfunction lowConfidenceResult(snapshot: WorkContextSnapshot, question?: string): WorkDiscoveryResult {\n return {\n projectSummary: snapshot.root.projectKind === 'coding'\n ? `${snapshot.root.displayName} appears to be a software project.`\n : `${snapshot.root.displayName} is a local work folder.`,\n currentState: snapshot.git?.changedPaths.length\n ? `${snapshot.git.changedPaths.length} changed paths were detected, but there was not enough reliable context to recommend work.`\n : 'There was not enough recent, readable context to determine the current objective.',\n uncertainties: ['The current objective is not explicit in the selected files.'],\n suggestions: [],\n lowConfidence: true,\n contextQuestion: question ?? 'What are you trying to move forward in this folder?',\n };\n}\n\nexport async function analyzeWorkContext(input: {\n config: Config;\n snapshot: WorkContextSnapshot;\n signal?: AbortSignal;\n}): Promise<{ modelRef: string; result: WorkDiscoveryResult }> {\n const modelRef = getAgentDefaultModelRef(input.config);\n if (!modelRef) throw new Error('No default model configured');\n if (input.snapshot.documents.length === 0 && !input.snapshot.git?.changedPaths.length) {\n return { modelRef, result: lowConfidenceResult(input.snapshot) };\n }\n\n const model = resolveModel(modelRef);\n const compactSnapshot = snapshotForModel(input.snapshot);\n const prompt = [\n 'You help a user resume real work in one explicitly selected local folder.',\n 'Analyze only the supplied bounded snapshot. Never claim that you ran commands, tests, or inspected anything absent from it.',\n 'Return only one JSON object with projectSummary, currentState, uncertainties, suggestions, lowConfidence, and contextQuestion.',\n 'For a normal result, suggestions must contain exactly 3 materially different next steps.',\n 'Each suggestion must include actionType, title, rationale, evidence, actionPrompt, confidence, expectedOutcome, estimatedMinutes, risk, and verification.',\n 'actionType must be summarize_recent_work, inspect_related_tests, or plan_next_step.',\n 'risk must be analysis, command, or file_write. estimatedMinutes is an integer from 1 to 30.',\n 'verification is a short array describing how the user can tell the outcome is real.',\n 'Each evidence item contains an optional exact relative path from the snapshot and one concrete observation.',\n 'actionPrompt asks the assistant to investigate or continue the step; it does not silently authorize file changes.',\n 'If the current objective is unclear or fewer than three credible suggestions exist, set lowConfidence=true, return suggestions=[], and ask one concise contextQuestion.',\n 'Use Simplified Chinese for every user-facing string when the visible snapshot documents are mainly Chinese; otherwise use English.',\n '',\n JSON.stringify(compactSnapshot),\n ].join('\\n');\n const message: UserMessage = { role: 'user', content: prompt, timestamp: Date.now() };\n const response = await completeWithResolvedCredentials(model, { messages: [message] }, {\n maxTokens: 2_500,\n temperature: 0.1,\n signal: input.signal,\n });\n const parsed = parseJson(extractText(response.content));\n if (!parsed) throw new Error('Analysis did not return valid JSON');\n\n const lowConfidence = parsed.lowConfidence === true;\n if (lowConfidence) {\n return {\n modelRef,\n result: lowConfidenceResult(\n input.snapshot,\n typeof parsed.contextQuestion === 'string' ? parsed.contextQuestion.trim().slice(0, 500) : undefined,\n ),\n };\n }\n\n const allowedPaths = new Set([\n ...input.snapshot.structure.sampledPaths,\n ...input.snapshot.documents.map((document) => document.relativePath),\n ...(input.snapshot.git?.changedPaths ?? []),\n ]);\n const suggestions = Array.isArray(parsed.suggestions)\n ? parsed.suggestions.map((value) => validateSuggestion(value, allowedPaths)).filter((value): value is WorkDiscoverySuggestion => Boolean(value))\n : [];\n if (suggestions.length !== 3) return { modelRef, result: lowConfidenceResult(input.snapshot) };\n const primarySuggestion = [...suggestions].sort((a, b) => suggestionScore(b) - suggestionScore(a))[0];\n const projectSummary = typeof parsed.projectSummary === 'string' ? parsed.projectSummary.trim() : '';\n const currentState = typeof parsed.currentState === 'string' ? parsed.currentState.trim() : '';\n if (!projectSummary || !currentState) throw new Error('Analysis result is missing its summary');\n return {\n modelRef,\n result: {\n projectSummary: projectSummary.slice(0, 1_200),\n currentState: currentState.slice(0, 1_200),\n uncertainties: strings(parsed.uncertainties, 6).map((value) => value.slice(0, 500)),\n suggestions,\n ...(primarySuggestion ? { primarySuggestionId: primarySuggestion.id } : {}),\n },\n };\n}\n\nexport function workDiscoveryResultMarkdown(result: WorkDiscoveryResult): string {\n const lines = [`## What I found`, '', result.projectSummary, '', result.currentState];\n if (result.lowConfidence) {\n if (result.contextQuestion) lines.push('', `**${result.contextQuestion}**`);\n return lines.join('\\n');\n }\n lines.push('', '## Suggested next steps');\n for (const [index, suggestion] of result.suggestions.entries()) {\n lines.push('', `### ${index + 1}. ${suggestion.title}`, '', suggestion.rationale, '', `Expected outcome: ${suggestion.expectedOutcome}`, '');\n for (const evidence of suggestion.evidence) {\n lines.push(`- ${evidence.path ? `\\`${evidence.path}\\`: ` : ''}${evidence.observation}`);\n }\n }\n return lines.join('\\n');\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { WORK_DISCOVERY_SCAN_POLICY_VERSION, canonicalWorkDiscoveryRoot, previewWorkDiscoveryRoot, probeWorkDiscoveryRoot, summarizeWorkContextSnapshot } from "./probe.js";
2
2
  import { analyzeWorkContext, workDiscoveryResultMarkdown } from "./analyzer.js";
3
- import { createWorkDiscoveryRun, getWorkDiscoveryOnboardingState, getWorkDiscoveryRun, getWorkDiscoveryRunByIdempotencyKey, setWorkDiscoveryOnboardingState, updateWorkDiscoveryRun } from "./repository.js";
3
+ import { createWorkDiscoveryRun, getWorkDiscoveryOnboardingState, getWorkDiscoveryRun, getWorkDiscoveryRunByIdempotencyKey, setWorkDiscoveryFeedback, setWorkDiscoveryOnboardingState, updateWorkDiscoveryRun } from "./repository.js";
4
4
  import { WorkDiscoveryService } from "./service.js";
5
- export { WORK_DISCOVERY_SCAN_POLICY_VERSION, WorkDiscoveryService, analyzeWorkContext, canonicalWorkDiscoveryRoot, createWorkDiscoveryRun, getWorkDiscoveryOnboardingState, getWorkDiscoveryRun, getWorkDiscoveryRunByIdempotencyKey, previewWorkDiscoveryRoot, probeWorkDiscoveryRoot, setWorkDiscoveryOnboardingState, summarizeWorkContextSnapshot, updateWorkDiscoveryRun, workDiscoveryResultMarkdown };
5
+ export { WORK_DISCOVERY_SCAN_POLICY_VERSION, WorkDiscoveryService, analyzeWorkContext, canonicalWorkDiscoveryRoot, createWorkDiscoveryRun, getWorkDiscoveryOnboardingState, getWorkDiscoveryRun, getWorkDiscoveryRunByIdempotencyKey, previewWorkDiscoveryRoot, probeWorkDiscoveryRoot, setWorkDiscoveryFeedback, setWorkDiscoveryOnboardingState, summarizeWorkContextSnapshot, updateWorkDiscoveryRun, workDiscoveryResultMarkdown };
@@ -7,6 +7,12 @@ export declare function previewWorkDiscoveryRoot(rootPath: string): Promise<{
7
7
  projectKind: import("../projects/project-kind.js").ProjectKind;
8
8
  projectKindConfidence: number;
9
9
  markerReasons: string[];
10
+ fingerprint: {
11
+ changedFileCount: number;
12
+ recentAreas: string[];
13
+ generatedAt: number;
14
+ branch?: string;
15
+ };
10
16
  }>;
11
17
  export declare function probeWorkDiscoveryRoot(rootPath: string, signal?: AbortSignal): Promise<WorkContextSnapshot>;
12
18
  export declare function summarizeWorkContextSnapshot(snapshot: WorkContextSnapshot): WorkContextSnapshotSummary;
@@ -180,12 +180,23 @@ async function canonicalWorkDiscoveryRoot(input) {
180
180
  async function previewWorkDiscoveryRoot(rootPath) {
181
181
  const canonicalRootPath = await canonicalWorkDiscoveryRoot(rootPath);
182
182
  const inference = inferProjectKind({ workspaceRoot: canonicalRootPath });
183
+ const git = await gitInfo(canonicalRootPath);
184
+ const recentAreas = Array.from(new Set((git?.changedPaths ?? []).map((path) => {
185
+ const [first, second] = path.replaceAll("\\", "/").split("/");
186
+ return second ? `${first}/${second}` : first;
187
+ }).filter((area) => Boolean(area)))).slice(0, 5);
183
188
  return {
184
189
  canonicalRootPath,
185
190
  displayName: basename(canonicalRootPath),
186
191
  projectKind: inference.kind,
187
192
  projectKindConfidence: inference.confidence,
188
- markerReasons: inference.reasons
193
+ markerReasons: inference.reasons,
194
+ fingerprint: {
195
+ ...git?.branch ? { branch: git.branch } : {},
196
+ changedFileCount: git?.changedPaths.length ?? 0,
197
+ recentAreas,
198
+ generatedAt: Date.now()
199
+ }
189
200
  };
190
201
  }
191
202
  async function probeWorkDiscoveryRoot(rootPath, signal) {
@@ -1 +1 @@
1
- {"version":3,"file":"probe.js","names":["fsConstants"],"sources":["../../../src/work-discovery/probe.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { constants as fsConstants } from 'node:fs';\nimport { access, lstat, readFile, readdir, realpath, stat } from 'node:fs/promises';\nimport { basename, extname, isAbsolute, relative, resolve, sep } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { inferProjectKind } from '../projects/index.js';\n\nimport type { WorkContextSnapshot, WorkContextSnapshotSummary } from './types.js';\n\nconst execFileAsync = promisify(execFile);\n\nexport const WORK_DISCOVERY_SCAN_POLICY_VERSION = 1;\n\nconst MAX_DEPTH = 4;\nconst MAX_CANDIDATE_FILES = 2_000;\nconst MAX_CONTENT_FILES = 30;\nconst MAX_FILE_BYTES = 64 * 1024;\nconst MAX_TOTAL_BYTES = 512 * 1024;\n\nconst EXCLUDED_DIRECTORY_NAMES = new Set([\n '.git', '.hg', '.svn', 'node_modules', 'vendor', 'dist', 'build', 'coverage', '.coverage',\n '.cache', '.next', '.nuxt', '.turbo', '.vite', '.pnpm-store', '__pycache__', '.venv', 'venv',\n 'target', 'out', 'Pods', 'DerivedData',\n]);\n\nconst BINARY_EXTENSIONS = new Set([\n '.7z', '.a', '.avi', '.bin', '.class', '.db', '.dmg', '.dll', '.eot', '.exe', '.gz', '.jar',\n '.lockb', '.mov', '.mp3', '.mp4', '.o', '.otf', '.pyc', '.rar', '.so', '.sqlite', '.sqlite3',\n '.tar', '.ttf', '.wav', '.webm', '.woff', '.woff2', '.zip',\n]);\n\nconst METADATA_ONLY_EXTENSIONS = new Map<string, 'document' | 'pdf' | 'image'>([\n ['.doc', 'document'], ['.docx', 'document'], ['.ppt', 'document'], ['.pptx', 'document'],\n ['.xls', 'document'], ['.xlsx', 'document'], ['.pdf', 'pdf'],\n ['.bmp', 'image'], ['.gif', 'image'], ['.ico', 'image'], ['.jpeg', 'image'], ['.jpg', 'image'],\n ['.png', 'image'], ['.webp', 'image'],\n]);\n\nconst SECRET_FILE_PATTERNS = [\n /^\\.env(?:\\.|$)/i,\n /(?:^|[-_.])(credentials?|secrets?|tokens?|api[-_]?keys?|auth)(?:[-_.]|$)/i,\n /\\.(?:pem|key|p12|pfx|crt|cer)$/i,\n /^(?:id_rsa|id_ed25519)(?:\\.pub)?$/i,\n];\n\nconst HIGH_VALUE_NAMES = [\n /^AGENTS\\.md$/i,\n /^CONTEXT\\.md$/i,\n /^README(?:\\..+)?$/i,\n /^TODO(?:\\..+)?$/i,\n /^ROADMAP(?:\\..+)?$/i,\n /^CHANGELOG(?:\\..+)?$/i,\n /^package\\.json$/i,\n /^pyproject\\.toml$/i,\n /^Cargo\\.toml$/i,\n /^go\\.mod$/i,\n];\n\nfunction abortIfNeeded(signal?: AbortSignal): void {\n if (signal?.aborted) throw new DOMException('Work discovery canceled', 'AbortError');\n}\n\nfunction isSecretFile(name: string): boolean {\n return SECRET_FILE_PATTERNS.some((pattern) => pattern.test(name));\n}\n\nfunction isExcludedDirectory(name: string): boolean {\n return EXCLUDED_DIRECTORY_NAMES.has(name) || name.startsWith('.');\n}\n\nfunction isLikelyBinary(path: string): boolean {\n return BINARY_EXTENSIONS.has(extname(path).toLowerCase());\n}\n\nfunction isInsideRoot(root: string, candidate: string): boolean {\n return candidate === root || candidate.startsWith(`${root}${sep}`);\n}\n\nfunction selectionPriority(path: string, modifiedAt: number): number {\n const name = basename(path);\n const highValueIndex = HIGH_VALUE_NAMES.findIndex((pattern) => pattern.test(name));\n if (highValueIndex >= 0) return 10_000 - highValueIndex * 100;\n return Math.floor(modifiedAt / 1_000_000);\n}\n\nasync function gitInfo(root: string, signal?: AbortSignal): Promise<WorkContextSnapshot['git'] | undefined> {\n abortIfNeeded(signal);\n try {\n const [{ stdout: statusOutput }, { stdout: logOutput }] = await Promise.all([\n execFileAsync('git', ['-C', root, 'status', '--short', '--branch', '--untracked-files=no'], {\n timeout: 4_000,\n maxBuffer: 256 * 1024,\n signal,\n }),\n execFileAsync('git', ['-C', root, 'log', '-10', '--format=%ct%x09%s'], {\n timeout: 4_000,\n maxBuffer: 256 * 1024,\n signal,\n }),\n ]);\n const statusLines = statusOutput.split('\\n').filter(Boolean);\n const branch = statusLines[0]?.startsWith('## ')\n ? statusLines[0].slice(3).split('...')[0]?.trim()\n : undefined;\n const changedPaths = statusLines.slice(1, 101).map((line) => line.slice(3).trim()).filter(Boolean);\n const recentCommits = logOutput.split('\\n').filter(Boolean).slice(0, 10).map((line) => {\n const [timestamp, ...subjectParts] = line.split('\\t');\n const committedAt = Number(timestamp) * 1000;\n return {\n subject: subjectParts.join('\\t').slice(0, 240),\n ...(Number.isFinite(committedAt) ? { committedAt } : {}),\n };\n });\n return { ...(branch ? { branch } : {}), changedPaths, recentCommits };\n } catch {\n return undefined;\n }\n}\n\nexport async function canonicalWorkDiscoveryRoot(input: string): Promise<string> {\n const trimmed = input.trim();\n if (!trimmed || !isAbsolute(trimmed)) throw new Error('An absolute folder path is required');\n const canonical = await realpath(trimmed);\n const info = await stat(canonical);\n if (!info.isDirectory()) throw new Error('Selected path is not a directory');\n await access(canonical, fsConstants.R_OK);\n return canonical;\n}\n\nexport async function previewWorkDiscoveryRoot(rootPath: string) {\n const canonicalRootPath = await canonicalWorkDiscoveryRoot(rootPath);\n const inference = inferProjectKind({ workspaceRoot: canonicalRootPath });\n return {\n canonicalRootPath,\n displayName: basename(canonicalRootPath),\n projectKind: inference.kind,\n projectKindConfidence: inference.confidence,\n markerReasons: inference.reasons,\n };\n}\n\nexport async function probeWorkDiscoveryRoot(\n rootPath: string,\n signal?: AbortSignal,\n): Promise<WorkContextSnapshot> {\n const root = await canonicalWorkDiscoveryRoot(rootPath);\n const inference = inferProjectKind({ workspaceRoot: root });\n const candidates: Array<{ absolutePath: string; relativePath: string; modifiedAt: number; size: number }> = [];\n const metadataOnlyFiles: WorkContextSnapshot['structure']['metadataOnlyFiles'] = [];\n let omittedPathCount = 0;\n let traversalTruncated = false;\n\n const walk = async (directory: string, depth: number): Promise<void> => {\n abortIfNeeded(signal);\n if (candidates.length >= MAX_CANDIDATE_FILES) {\n traversalTruncated = true;\n return;\n }\n let entries;\n try {\n entries = await readdir(directory, { withFileTypes: true });\n } catch {\n return;\n }\n entries.sort((a, b) => a.name.localeCompare(b.name));\n for (const entry of entries) {\n abortIfNeeded(signal);\n if (candidates.length >= MAX_CANDIDATE_FILES) {\n traversalTruncated = true;\n omittedPathCount += 1;\n continue;\n }\n if (entry.isDirectory()) {\n if (depth >= MAX_DEPTH || isExcludedDirectory(entry.name)) {\n omittedPathCount += 1;\n continue;\n }\n await walk(resolve(directory, entry.name), depth + 1);\n continue;\n }\n if (!entry.isFile() || isSecretFile(entry.name)) {\n omittedPathCount += 1;\n continue;\n }\n const absolutePath = resolve(directory, entry.name);\n try {\n const resolvedPath = await realpath(absolutePath);\n if (!isInsideRoot(root, resolvedPath)) {\n omittedPathCount += 1;\n continue;\n }\n const info = await lstat(resolvedPath);\n if (!info.isFile()) {\n omittedPathCount += 1;\n continue;\n }\n const relativePath = relative(root, resolvedPath);\n const metadataKind = METADATA_ONLY_EXTENSIONS.get(extname(entry.name).toLowerCase());\n if (metadataKind) {\n if (metadataOnlyFiles.length < 200) {\n metadataOnlyFiles.push({ relativePath, modifiedAt: info.mtimeMs, size: info.size, kind: metadataKind });\n } else {\n omittedPathCount += 1;\n }\n continue;\n }\n if (isLikelyBinary(entry.name) || info.size > MAX_FILE_BYTES * 4) {\n omittedPathCount += 1;\n continue;\n }\n candidates.push({\n absolutePath: resolvedPath,\n relativePath,\n modifiedAt: info.mtimeMs,\n size: info.size,\n });\n } catch {\n omittedPathCount += 1;\n }\n }\n };\n\n await walk(root, 0);\n const git = await gitInfo(root, signal);\n const changed = new Set(git?.changedPaths ?? []);\n candidates.sort((a, b) => {\n const aScore = selectionPriority(a.relativePath, a.modifiedAt) + (changed.has(a.relativePath) ? 20_000 : 0);\n const bScore = selectionPriority(b.relativePath, b.modifiedAt) + (changed.has(b.relativePath) ? 20_000 : 0);\n return bScore - aScore || a.relativePath.localeCompare(b.relativePath);\n });\n\n const documents: WorkContextSnapshot['documents'] = [];\n let contentBytes = 0;\n for (const candidate of candidates) {\n abortIfNeeded(signal);\n if (documents.length >= MAX_CONTENT_FILES || contentBytes >= MAX_TOTAL_BYTES) break;\n const remaining = MAX_TOTAL_BYTES - contentBytes;\n const limit = Math.min(MAX_FILE_BYTES, remaining);\n try {\n const buffer = await readFile(candidate.absolutePath);\n const slice = buffer.subarray(0, limit);\n if (slice.includes(0)) continue;\n const excerpt = slice.toString('utf8').trim();\n if (!excerpt) continue;\n const highValue = HIGH_VALUE_NAMES.some((pattern) => pattern.test(basename(candidate.relativePath)));\n documents.push({\n relativePath: candidate.relativePath,\n modifiedAt: candidate.modifiedAt,\n excerpt,\n truncated: buffer.length > slice.length,\n selectionReason: changed.has(candidate.relativePath)\n ? 'git_change'\n : highValue\n ? 'project_context'\n : 'recently_modified',\n });\n contentBytes += slice.length;\n } catch {\n // Continue with the remaining readable files.\n }\n }\n\n return {\n root: {\n displayName: basename(root),\n projectKind: inference.kind,\n markerReasons: inference.reasons,\n },\n structure: {\n sampledPaths: [\n ...candidates.map((candidate) => candidate.relativePath),\n ...metadataOnlyFiles.map((file) => file.relativePath),\n ].slice(0, 200),\n metadataOnlyFiles,\n omittedPathCount,\n },\n ...(git ? { git } : {}),\n documents,\n limits: {\n policyVersion: WORK_DISCOVERY_SCAN_POLICY_VERSION,\n fileCount: candidates.length,\n contentBytes,\n truncated: traversalTruncated || candidates.length > 200 || documents.some((document) => document.truncated),\n },\n };\n}\n\nexport function summarizeWorkContextSnapshot(snapshot: WorkContextSnapshot): WorkContextSnapshotSummary {\n return {\n projectKind: snapshot.root.projectKind,\n sampledPathCount: snapshot.structure.sampledPaths.length,\n omittedPathCount: snapshot.structure.omittedPathCount,\n documentCount: snapshot.documents.length,\n contentBytes: snapshot.limits.contentBytes,\n changedPathCount: snapshot.git?.changedPaths.length ?? 0,\n truncated: snapshot.limits.truncated,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"probe.js","names":["fsConstants"],"sources":["../../../src/work-discovery/probe.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { constants as fsConstants } from 'node:fs';\nimport { access, lstat, readFile, readdir, realpath, stat } from 'node:fs/promises';\nimport { basename, extname, isAbsolute, relative, resolve, sep } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { inferProjectKind } from '../projects/index.js';\n\nimport type { WorkContextSnapshot, WorkContextSnapshotSummary } from './types.js';\n\nconst execFileAsync = promisify(execFile);\n\nexport const WORK_DISCOVERY_SCAN_POLICY_VERSION = 1;\n\nconst MAX_DEPTH = 4;\nconst MAX_CANDIDATE_FILES = 2_000;\nconst MAX_CONTENT_FILES = 30;\nconst MAX_FILE_BYTES = 64 * 1024;\nconst MAX_TOTAL_BYTES = 512 * 1024;\n\nconst EXCLUDED_DIRECTORY_NAMES = new Set([\n '.git', '.hg', '.svn', 'node_modules', 'vendor', 'dist', 'build', 'coverage', '.coverage',\n '.cache', '.next', '.nuxt', '.turbo', '.vite', '.pnpm-store', '__pycache__', '.venv', 'venv',\n 'target', 'out', 'Pods', 'DerivedData',\n]);\n\nconst BINARY_EXTENSIONS = new Set([\n '.7z', '.a', '.avi', '.bin', '.class', '.db', '.dmg', '.dll', '.eot', '.exe', '.gz', '.jar',\n '.lockb', '.mov', '.mp3', '.mp4', '.o', '.otf', '.pyc', '.rar', '.so', '.sqlite', '.sqlite3',\n '.tar', '.ttf', '.wav', '.webm', '.woff', '.woff2', '.zip',\n]);\n\nconst METADATA_ONLY_EXTENSIONS = new Map<string, 'document' | 'pdf' | 'image'>([\n ['.doc', 'document'], ['.docx', 'document'], ['.ppt', 'document'], ['.pptx', 'document'],\n ['.xls', 'document'], ['.xlsx', 'document'], ['.pdf', 'pdf'],\n ['.bmp', 'image'], ['.gif', 'image'], ['.ico', 'image'], ['.jpeg', 'image'], ['.jpg', 'image'],\n ['.png', 'image'], ['.webp', 'image'],\n]);\n\nconst SECRET_FILE_PATTERNS = [\n /^\\.env(?:\\.|$)/i,\n /(?:^|[-_.])(credentials?|secrets?|tokens?|api[-_]?keys?|auth)(?:[-_.]|$)/i,\n /\\.(?:pem|key|p12|pfx|crt|cer)$/i,\n /^(?:id_rsa|id_ed25519)(?:\\.pub)?$/i,\n];\n\nconst HIGH_VALUE_NAMES = [\n /^AGENTS\\.md$/i,\n /^CONTEXT\\.md$/i,\n /^README(?:\\..+)?$/i,\n /^TODO(?:\\..+)?$/i,\n /^ROADMAP(?:\\..+)?$/i,\n /^CHANGELOG(?:\\..+)?$/i,\n /^package\\.json$/i,\n /^pyproject\\.toml$/i,\n /^Cargo\\.toml$/i,\n /^go\\.mod$/i,\n];\n\nfunction abortIfNeeded(signal?: AbortSignal): void {\n if (signal?.aborted) throw new DOMException('Work discovery canceled', 'AbortError');\n}\n\nfunction isSecretFile(name: string): boolean {\n return SECRET_FILE_PATTERNS.some((pattern) => pattern.test(name));\n}\n\nfunction isExcludedDirectory(name: string): boolean {\n return EXCLUDED_DIRECTORY_NAMES.has(name) || name.startsWith('.');\n}\n\nfunction isLikelyBinary(path: string): boolean {\n return BINARY_EXTENSIONS.has(extname(path).toLowerCase());\n}\n\nfunction isInsideRoot(root: string, candidate: string): boolean {\n return candidate === root || candidate.startsWith(`${root}${sep}`);\n}\n\nfunction selectionPriority(path: string, modifiedAt: number): number {\n const name = basename(path);\n const highValueIndex = HIGH_VALUE_NAMES.findIndex((pattern) => pattern.test(name));\n if (highValueIndex >= 0) return 10_000 - highValueIndex * 100;\n return Math.floor(modifiedAt / 1_000_000);\n}\n\nasync function gitInfo(root: string, signal?: AbortSignal): Promise<WorkContextSnapshot['git'] | undefined> {\n abortIfNeeded(signal);\n try {\n const [{ stdout: statusOutput }, { stdout: logOutput }] = await Promise.all([\n execFileAsync('git', ['-C', root, 'status', '--short', '--branch', '--untracked-files=no'], {\n timeout: 4_000,\n maxBuffer: 256 * 1024,\n signal,\n }),\n execFileAsync('git', ['-C', root, 'log', '-10', '--format=%ct%x09%s'], {\n timeout: 4_000,\n maxBuffer: 256 * 1024,\n signal,\n }),\n ]);\n const statusLines = statusOutput.split('\\n').filter(Boolean);\n const branch = statusLines[0]?.startsWith('## ')\n ? statusLines[0].slice(3).split('...')[0]?.trim()\n : undefined;\n const changedPaths = statusLines.slice(1, 101).map((line) => line.slice(3).trim()).filter(Boolean);\n const recentCommits = logOutput.split('\\n').filter(Boolean).slice(0, 10).map((line) => {\n const [timestamp, ...subjectParts] = line.split('\\t');\n const committedAt = Number(timestamp) * 1000;\n return {\n subject: subjectParts.join('\\t').slice(0, 240),\n ...(Number.isFinite(committedAt) ? { committedAt } : {}),\n };\n });\n return { ...(branch ? { branch } : {}), changedPaths, recentCommits };\n } catch {\n return undefined;\n }\n}\n\nexport async function canonicalWorkDiscoveryRoot(input: string): Promise<string> {\n const trimmed = input.trim();\n if (!trimmed || !isAbsolute(trimmed)) throw new Error('An absolute folder path is required');\n const canonical = await realpath(trimmed);\n const info = await stat(canonical);\n if (!info.isDirectory()) throw new Error('Selected path is not a directory');\n await access(canonical, fsConstants.R_OK);\n return canonical;\n}\n\nexport async function previewWorkDiscoveryRoot(rootPath: string) {\n const canonicalRootPath = await canonicalWorkDiscoveryRoot(rootPath);\n const inference = inferProjectKind({ workspaceRoot: canonicalRootPath });\n const git = await gitInfo(canonicalRootPath);\n const recentAreas = Array.from(new Set((git?.changedPaths ?? []).map((path) => {\n const normalized = path.replaceAll('\\\\', '/');\n const [first, second] = normalized.split('/');\n return second ? `${first}/${second}` : first;\n }).filter((area): area is string => Boolean(area)))).slice(0, 5);\n return {\n canonicalRootPath,\n displayName: basename(canonicalRootPath),\n projectKind: inference.kind,\n projectKindConfidence: inference.confidence,\n markerReasons: inference.reasons,\n fingerprint: {\n ...(git?.branch ? { branch: git.branch } : {}),\n changedFileCount: git?.changedPaths.length ?? 0,\n recentAreas,\n generatedAt: Date.now(),\n },\n };\n}\n\nexport async function probeWorkDiscoveryRoot(\n rootPath: string,\n signal?: AbortSignal,\n): Promise<WorkContextSnapshot> {\n const root = await canonicalWorkDiscoveryRoot(rootPath);\n const inference = inferProjectKind({ workspaceRoot: root });\n const candidates: Array<{ absolutePath: string; relativePath: string; modifiedAt: number; size: number }> = [];\n const metadataOnlyFiles: WorkContextSnapshot['structure']['metadataOnlyFiles'] = [];\n let omittedPathCount = 0;\n let traversalTruncated = false;\n\n const walk = async (directory: string, depth: number): Promise<void> => {\n abortIfNeeded(signal);\n if (candidates.length >= MAX_CANDIDATE_FILES) {\n traversalTruncated = true;\n return;\n }\n let entries;\n try {\n entries = await readdir(directory, { withFileTypes: true });\n } catch {\n return;\n }\n entries.sort((a, b) => a.name.localeCompare(b.name));\n for (const entry of entries) {\n abortIfNeeded(signal);\n if (candidates.length >= MAX_CANDIDATE_FILES) {\n traversalTruncated = true;\n omittedPathCount += 1;\n continue;\n }\n if (entry.isDirectory()) {\n if (depth >= MAX_DEPTH || isExcludedDirectory(entry.name)) {\n omittedPathCount += 1;\n continue;\n }\n await walk(resolve(directory, entry.name), depth + 1);\n continue;\n }\n if (!entry.isFile() || isSecretFile(entry.name)) {\n omittedPathCount += 1;\n continue;\n }\n const absolutePath = resolve(directory, entry.name);\n try {\n const resolvedPath = await realpath(absolutePath);\n if (!isInsideRoot(root, resolvedPath)) {\n omittedPathCount += 1;\n continue;\n }\n const info = await lstat(resolvedPath);\n if (!info.isFile()) {\n omittedPathCount += 1;\n continue;\n }\n const relativePath = relative(root, resolvedPath);\n const metadataKind = METADATA_ONLY_EXTENSIONS.get(extname(entry.name).toLowerCase());\n if (metadataKind) {\n if (metadataOnlyFiles.length < 200) {\n metadataOnlyFiles.push({ relativePath, modifiedAt: info.mtimeMs, size: info.size, kind: metadataKind });\n } else {\n omittedPathCount += 1;\n }\n continue;\n }\n if (isLikelyBinary(entry.name) || info.size > MAX_FILE_BYTES * 4) {\n omittedPathCount += 1;\n continue;\n }\n candidates.push({\n absolutePath: resolvedPath,\n relativePath,\n modifiedAt: info.mtimeMs,\n size: info.size,\n });\n } catch {\n omittedPathCount += 1;\n }\n }\n };\n\n await walk(root, 0);\n const git = await gitInfo(root, signal);\n const changed = new Set(git?.changedPaths ?? []);\n candidates.sort((a, b) => {\n const aScore = selectionPriority(a.relativePath, a.modifiedAt) + (changed.has(a.relativePath) ? 20_000 : 0);\n const bScore = selectionPriority(b.relativePath, b.modifiedAt) + (changed.has(b.relativePath) ? 20_000 : 0);\n return bScore - aScore || a.relativePath.localeCompare(b.relativePath);\n });\n\n const documents: WorkContextSnapshot['documents'] = [];\n let contentBytes = 0;\n for (const candidate of candidates) {\n abortIfNeeded(signal);\n if (documents.length >= MAX_CONTENT_FILES || contentBytes >= MAX_TOTAL_BYTES) break;\n const remaining = MAX_TOTAL_BYTES - contentBytes;\n const limit = Math.min(MAX_FILE_BYTES, remaining);\n try {\n const buffer = await readFile(candidate.absolutePath);\n const slice = buffer.subarray(0, limit);\n if (slice.includes(0)) continue;\n const excerpt = slice.toString('utf8').trim();\n if (!excerpt) continue;\n const highValue = HIGH_VALUE_NAMES.some((pattern) => pattern.test(basename(candidate.relativePath)));\n documents.push({\n relativePath: candidate.relativePath,\n modifiedAt: candidate.modifiedAt,\n excerpt,\n truncated: buffer.length > slice.length,\n selectionReason: changed.has(candidate.relativePath)\n ? 'git_change'\n : highValue\n ? 'project_context'\n : 'recently_modified',\n });\n contentBytes += slice.length;\n } catch {\n // Continue with the remaining readable files.\n }\n }\n\n return {\n root: {\n displayName: basename(root),\n projectKind: inference.kind,\n markerReasons: inference.reasons,\n },\n structure: {\n sampledPaths: [\n ...candidates.map((candidate) => candidate.relativePath),\n ...metadataOnlyFiles.map((file) => file.relativePath),\n ].slice(0, 200),\n metadataOnlyFiles,\n omittedPathCount,\n },\n ...(git ? { git } : {}),\n documents,\n limits: {\n policyVersion: WORK_DISCOVERY_SCAN_POLICY_VERSION,\n fileCount: candidates.length,\n contentBytes,\n truncated: traversalTruncated || candidates.length > 200 || documents.some((document) => document.truncated),\n },\n };\n}\n\nexport function summarizeWorkContextSnapshot(snapshot: WorkContextSnapshot): WorkContextSnapshotSummary {\n return {\n projectKind: snapshot.root.projectKind,\n sampledPathCount: snapshot.structure.sampledPaths.length,\n omittedPathCount: snapshot.structure.omittedPathCount,\n documentCount: snapshot.documents.length,\n contentBytes: snapshot.limits.contentBytes,\n changedPathCount: snapshot.git?.changedPaths.length ?? 0,\n truncated: snapshot.limits.truncated,\n };\n}\n"],"mappings":";;;;;;;;AAUA,MAAM,gBAAgB,UAAU,SAAS;AAEzC,MAAa,qCAAqC;AAElD,MAAM,YAAY;AAClB,MAAM,sBAAsB;AAC5B,MAAM,oBAAoB;AAC1B,MAAM,iBAAiB,KAAK;AAC5B,MAAM,kBAAkB,MAAM;AAE9B,MAAM,2BAA2B,IAAI,IAAI;CACvC;CAAQ;CAAO;CAAQ;CAAgB;CAAU;CAAQ;CAAS;CAAY;CAC9E;CAAU;CAAS;CAAS;CAAU;CAAS;CAAe;CAAe;CAAS;CACtF;CAAU;CAAO;CAAQ;CAC1B,CAAC;AAEF,MAAM,oBAAoB,IAAI,IAAI;CAChC;CAAO;CAAM;CAAQ;CAAQ;CAAU;CAAO;CAAQ;CAAQ;CAAQ;CAAQ;CAAO;CACrF;CAAU;CAAQ;CAAQ;CAAQ;CAAM;CAAQ;CAAQ;CAAQ;CAAO;CAAW;CAClF;CAAQ;CAAQ;CAAQ;CAAS;CAAS;CAAU;CACrD,CAAC;AAEF,MAAM,2BAA2B,IAAI,IAA0C;CAC7E,CAAC,QAAQ,WAAW;CAAE,CAAC,SAAS,WAAW;CAAE,CAAC,QAAQ,WAAW;CAAE,CAAC,SAAS,WAAW;CACxF,CAAC,QAAQ,WAAW;CAAE,CAAC,SAAS,WAAW;CAAE,CAAC,QAAQ,MAAM;CAC5D,CAAC,QAAQ,QAAQ;CAAE,CAAC,QAAQ,QAAQ;CAAE,CAAC,QAAQ,QAAQ;CAAE,CAAC,SAAS,QAAQ;CAAE,CAAC,QAAQ,QAAQ;CAC9F,CAAC,QAAQ,QAAQ;CAAE,CAAC,SAAS,QAAQ;CACtC,CAAC;AAEF,MAAM,uBAAuB;CAC3B;CACA;CACA;CACA;CACD;AAED,MAAM,mBAAmB;CACvB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,SAAS,cAAc,QAA4B;AACjD,KAAI,QAAQ,QAAS,OAAM,IAAI,aAAa,2BAA2B,aAAa;;AAGtF,SAAS,aAAa,MAAuB;AAC3C,QAAO,qBAAqB,MAAM,YAAY,QAAQ,KAAK,KAAK,CAAC;;AAGnE,SAAS,oBAAoB,MAAuB;AAClD,QAAO,yBAAyB,IAAI,KAAK,IAAI,KAAK,WAAW,IAAI;;AAGnE,SAAS,eAAe,MAAuB;AAC7C,QAAO,kBAAkB,IAAI,QAAQ,KAAK,CAAC,aAAa,CAAC;;AAG3D,SAAS,aAAa,MAAc,WAA4B;AAC9D,QAAO,cAAc,QAAQ,UAAU,WAAW,GAAG,OAAO,MAAM;;AAGpE,SAAS,kBAAkB,MAAc,YAA4B;CACnE,MAAM,OAAO,SAAS,KAAK;CAC3B,MAAM,iBAAiB,iBAAiB,WAAW,YAAY,QAAQ,KAAK,KAAK,CAAC;AAClF,KAAI,kBAAkB,EAAG,QAAO,MAAS,iBAAiB;AAC1D,QAAO,KAAK,MAAM,aAAa,IAAU;;AAG3C,eAAe,QAAQ,MAAc,QAAuE;AAC1G,eAAc,OAAO;AACrB,KAAI;EACF,MAAM,CAAC,EAAE,QAAQ,gBAAgB,EAAE,QAAQ,eAAe,MAAM,QAAQ,IAAI,CAC1E,cAAc,OAAO;GAAC;GAAM;GAAM;GAAU;GAAW;GAAY;GAAuB,EAAE;GAC1F,SAAS;GACT,WAAW,MAAM;GACjB;GACD,CAAC,EACF,cAAc,OAAO;GAAC;GAAM;GAAM;GAAO;GAAO;GAAqB,EAAE;GACrE,SAAS;GACT,WAAW,MAAM;GACjB;GACD,CAAC,CACH,CAAC;EACF,MAAM,cAAc,aAAa,MAAM,KAAK,CAAC,OAAO,QAAQ;EAC5D,MAAM,SAAS,YAAY,IAAI,WAAW,MAAM,GAC5C,YAAY,GAAG,MAAM,EAAE,CAAC,MAAM,MAAM,CAAC,IAAI,MAAM,GAC/C,KAAA;EACJ,MAAM,eAAe,YAAY,MAAM,GAAG,IAAI,CAAC,KAAK,SAAS,KAAK,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC,OAAO,QAAQ;EAClG,MAAM,gBAAgB,UAAU,MAAM,KAAK,CAAC,OAAO,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC,KAAK,SAAS;GACrF,MAAM,CAAC,WAAW,GAAG,gBAAgB,KAAK,MAAM,IAAK;GACrD,MAAM,cAAc,OAAO,UAAU,GAAG;AACxC,UAAO;IACL,SAAS,aAAa,KAAK,IAAK,CAAC,MAAM,GAAG,IAAI;IAC9C,GAAI,OAAO,SAAS,YAAY,GAAG,EAAE,aAAa,GAAG,EAAE;IACxD;IACD;AACF,SAAO;GAAE,GAAI,SAAS,EAAE,QAAQ,GAAG,EAAE;GAAG;GAAc;GAAe;SAC/D;AACN;;;AAIJ,eAAsB,2BAA2B,OAAgC;CAC/E,MAAM,UAAU,MAAM,MAAM;AAC5B,KAAI,CAAC,WAAW,CAAC,WAAW,QAAQ,CAAE,OAAM,IAAI,MAAM,sCAAsC;CAC5F,MAAM,YAAY,MAAM,SAAS,QAAQ;AAEzC,KAAI,EAAC,MADc,KAAK,UAAU,EACxB,aAAa,CAAE,OAAM,IAAI,MAAM,mCAAmC;AAC5E,OAAM,OAAO,WAAWA,UAAY,KAAK;AACzC,QAAO;;AAGT,eAAsB,yBAAyB,UAAkB;CAC/D,MAAM,oBAAoB,MAAM,2BAA2B,SAAS;CACpE,MAAM,YAAY,iBAAiB,EAAE,eAAe,mBAAmB,CAAC;CACxE,MAAM,MAAM,MAAM,QAAQ,kBAAkB;CAC5C,MAAM,cAAc,MAAM,KAAK,IAAI,KAAK,KAAK,gBAAgB,EAAE,EAAE,KAAK,SAAS;EAE7E,MAAM,CAAC,OAAO,UADK,KAAK,WAAW,MAAM,IACP,CAAC,MAAM,IAAI;AAC7C,SAAO,SAAS,GAAG,MAAM,GAAG,WAAW;GACvC,CAAC,QAAQ,SAAyB,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,EAAE;AAChE,QAAO;EACL;EACA,aAAa,SAAS,kBAAkB;EACxC,aAAa,UAAU;EACvB,uBAAuB,UAAU;EACjC,eAAe,UAAU;EACzB,aAAa;GACX,GAAI,KAAK,SAAS,EAAE,QAAQ,IAAI,QAAQ,GAAG,EAAE;GAC7C,kBAAkB,KAAK,aAAa,UAAU;GAC9C;GACA,aAAa,KAAK,KAAK;GACxB;EACF;;AAGH,eAAsB,uBACpB,UACA,QAC8B;CAC9B,MAAM,OAAO,MAAM,2BAA2B,SAAS;CACvD,MAAM,YAAY,iBAAiB,EAAE,eAAe,MAAM,CAAC;CAC3D,MAAM,aAAsG,EAAE;CAC9G,MAAM,oBAA2E,EAAE;CACnF,IAAI,mBAAmB;CACvB,IAAI,qBAAqB;CAEzB,MAAM,OAAO,OAAO,WAAmB,UAAiC;AACtE,gBAAc,OAAO;AACrB,MAAI,WAAW,UAAU,qBAAqB;AAC5C,wBAAqB;AACrB;;EAEF,IAAI;AACJ,MAAI;AACF,aAAU,MAAM,QAAQ,WAAW,EAAE,eAAe,MAAM,CAAC;UACrD;AACN;;AAEF,UAAQ,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,KAAK,CAAC;AACpD,OAAK,MAAM,SAAS,SAAS;AAC3B,iBAAc,OAAO;AACrB,OAAI,WAAW,UAAU,qBAAqB;AAC5C,yBAAqB;AACrB,wBAAoB;AACpB;;AAEF,OAAI,MAAM,aAAa,EAAE;AACvB,QAAI,SAAS,aAAa,oBAAoB,MAAM,KAAK,EAAE;AACzD,yBAAoB;AACpB;;AAEF,UAAM,KAAK,QAAQ,WAAW,MAAM,KAAK,EAAE,QAAQ,EAAE;AACrD;;AAEF,OAAI,CAAC,MAAM,QAAQ,IAAI,aAAa,MAAM,KAAK,EAAE;AAC/C,wBAAoB;AACpB;;GAEF,MAAM,eAAe,QAAQ,WAAW,MAAM,KAAK;AACnD,OAAI;IACF,MAAM,eAAe,MAAM,SAAS,aAAa;AACjD,QAAI,CAAC,aAAa,MAAM,aAAa,EAAE;AACrC,yBAAoB;AACpB;;IAEF,MAAM,OAAO,MAAM,MAAM,aAAa;AACtC,QAAI,CAAC,KAAK,QAAQ,EAAE;AAClB,yBAAoB;AACpB;;IAEF,MAAM,eAAe,SAAS,MAAM,aAAa;IACjD,MAAM,eAAe,yBAAyB,IAAI,QAAQ,MAAM,KAAK,CAAC,aAAa,CAAC;AACpF,QAAI,cAAc;AAChB,SAAI,kBAAkB,SAAS,IAC7B,mBAAkB,KAAK;MAAE;MAAc,YAAY,KAAK;MAAS,MAAM,KAAK;MAAM,MAAM;MAAc,CAAC;SAEvG,qBAAoB;AAEtB;;AAEF,QAAI,eAAe,MAAM,KAAK,IAAI,KAAK,OAAO,iBAAiB,GAAG;AAChE,yBAAoB;AACpB;;AAEF,eAAW,KAAK;KACd,cAAc;KACd;KACA,YAAY,KAAK;KACjB,MAAM,KAAK;KACZ,CAAC;WACI;AACN,wBAAoB;;;;AAK1B,OAAM,KAAK,MAAM,EAAE;CACnB,MAAM,MAAM,MAAM,QAAQ,MAAM,OAAO;CACvC,MAAM,UAAU,IAAI,IAAI,KAAK,gBAAgB,EAAE,CAAC;AAChD,YAAW,MAAM,GAAG,MAAM;EACxB,MAAM,SAAS,kBAAkB,EAAE,cAAc,EAAE,WAAW,IAAI,QAAQ,IAAI,EAAE,aAAa,GAAG,MAAS;AAEzG,SADe,kBAAkB,EAAE,cAAc,EAAE,WAAW,IAAI,QAAQ,IAAI,EAAE,aAAa,GAAG,MAAS,KACzF,UAAU,EAAE,aAAa,cAAc,EAAE,aAAa;GACtE;CAEF,MAAM,YAA8C,EAAE;CACtD,IAAI,eAAe;AACnB,MAAK,MAAM,aAAa,YAAY;AAClC,gBAAc,OAAO;AACrB,MAAI,UAAU,UAAU,qBAAqB,gBAAgB,gBAAiB;EAC9E,MAAM,YAAY,kBAAkB;EACpC,MAAM,QAAQ,KAAK,IAAI,gBAAgB,UAAU;AACjD,MAAI;GACF,MAAM,SAAS,MAAM,SAAS,UAAU,aAAa;GACrD,MAAM,QAAQ,OAAO,SAAS,GAAG,MAAM;AACvC,OAAI,MAAM,SAAS,EAAE,CAAE;GACvB,MAAM,UAAU,MAAM,SAAS,OAAO,CAAC,MAAM;AAC7C,OAAI,CAAC,QAAS;GACd,MAAM,YAAY,iBAAiB,MAAM,YAAY,QAAQ,KAAK,SAAS,UAAU,aAAa,CAAC,CAAC;AACpG,aAAU,KAAK;IACb,cAAc,UAAU;IACxB,YAAY,UAAU;IACtB;IACA,WAAW,OAAO,SAAS,MAAM;IACjC,iBAAiB,QAAQ,IAAI,UAAU,aAAa,GAChD,eACA,YACE,oBACA;IACP,CAAC;AACF,mBAAgB,MAAM;UAChB;;AAKV,QAAO;EACL,MAAM;GACJ,aAAa,SAAS,KAAK;GAC3B,aAAa,UAAU;GACvB,eAAe,UAAU;GAC1B;EACD,WAAW;GACT,cAAc,CACZ,GAAG,WAAW,KAAK,cAAc,UAAU,aAAa,EACxD,GAAG,kBAAkB,KAAK,SAAS,KAAK,aAAa,CACtD,CAAC,MAAM,GAAG,IAAI;GACf;GACA;GACD;EACD,GAAI,MAAM,EAAE,KAAK,GAAG,EAAE;EACtB;EACA,QAAQ;GACN,eAAA;GACA,WAAW,WAAW;GACtB;GACA,WAAW,sBAAsB,WAAW,SAAS,OAAO,UAAU,MAAM,aAAa,SAAS,UAAU;GAC7G;EACF;;AAGH,SAAgB,6BAA6B,UAA2D;AACtG,QAAO;EACL,aAAa,SAAS,KAAK;EAC3B,kBAAkB,SAAS,UAAU,aAAa;EAClD,kBAAkB,SAAS,UAAU;EACrC,eAAe,SAAS,UAAU;EAClC,cAAc,SAAS,OAAO;EAC9B,kBAAkB,SAAS,KAAK,aAAa,UAAU;EACvD,WAAW,SAAS,OAAO;EAC5B"}
@@ -1,4 +1,4 @@
1
- import type { WorkDiscoveryOnboardingState, WorkDiscoveryOnboardingStatus, WorkDiscoveryRun } from './types.js';
1
+ import type { WorkDiscoveryOnboardingState, WorkDiscoveryOnboardingStatus, WorkDiscoveryFeedback, WorkDiscoveryRecognitionDecision, WorkDiscoveryRun } from './types.js';
2
2
  export declare function getWorkDiscoveryOnboardingState(): WorkDiscoveryOnboardingState;
3
3
  export declare function setWorkDiscoveryOnboardingState(input: {
4
4
  status: WorkDiscoveryOnboardingStatus;
@@ -8,4 +8,10 @@ export declare function setWorkDiscoveryOnboardingState(input: {
8
8
  export declare function createWorkDiscoveryRun(run: WorkDiscoveryRun): WorkDiscoveryRun;
9
9
  export declare function getWorkDiscoveryRun(id: string): WorkDiscoveryRun | null;
10
10
  export declare function getWorkDiscoveryRunByIdempotencyKey(key: string): WorkDiscoveryRun | null;
11
+ export declare function setWorkDiscoveryFeedback(input: {
12
+ runId: string;
13
+ recognitionDecision: WorkDiscoveryRecognitionDecision;
14
+ correctedIntent?: string;
15
+ nowMs?: number;
16
+ }): WorkDiscoveryFeedback;
11
17
  export declare function updateWorkDiscoveryRun(id: string, patch: Partial<Pick<WorkDiscoveryRun, 'status' | 'stage' | 'snapshot' | 'result' | 'errorCode' | 'errorMessage' | 'startedAt' | 'completedAt' | 'canceledAt'>>): WorkDiscoveryRun | null;
@@ -43,7 +43,26 @@ function runFromRow(row) {
43
43
  }
44
44
  function readRun(db, clause, value) {
45
45
  const row = db.prepare(`SELECT * FROM work_discovery_runs WHERE ${clause} = ?`).get(value);
46
- return row ? runFromRow(row) : null;
46
+ if (!row) return null;
47
+ const run = runFromRow(row);
48
+ const feedback = getWorkDiscoveryFeedbackFromDb(db, run.id);
49
+ return feedback ? {
50
+ ...run,
51
+ feedback
52
+ } : run;
53
+ }
54
+ function feedbackFromRow(row) {
55
+ return {
56
+ runId: row.run_id,
57
+ recognitionDecision: row.recognition_decision,
58
+ ...row.corrected_intent ? { correctedIntent: row.corrected_intent } : {},
59
+ createdAt: row.created_at,
60
+ updatedAt: row.updated_at
61
+ };
62
+ }
63
+ function getWorkDiscoveryFeedbackFromDb(db, runId) {
64
+ const row = db.prepare("SELECT * FROM work_discovery_feedback WHERE run_id = ?").get(runId);
65
+ return row ? feedbackFromRow(row) : null;
47
66
  }
48
67
  function getWorkDiscoveryOnboardingState() {
49
68
  const { db } = requireXopcDatabase();
@@ -79,6 +98,20 @@ function getWorkDiscoveryRunByIdempotencyKey(key) {
79
98
  const { db } = requireXopcDatabase();
80
99
  return readRun(db, "idempotency_key", key);
81
100
  }
101
+ function setWorkDiscoveryFeedback(input) {
102
+ const now = input.nowMs ?? Date.now();
103
+ runSqliteWriteTransaction((db) => {
104
+ db.prepare(`INSERT INTO work_discovery_feedback (
105
+ run_id, recognition_decision, corrected_intent, created_at, updated_at
106
+ ) VALUES (?, ?, ?, ?, ?)
107
+ ON CONFLICT(run_id) DO UPDATE SET
108
+ recognition_decision = excluded.recognition_decision,
109
+ corrected_intent = excluded.corrected_intent,
110
+ updated_at = excluded.updated_at`).run(input.runId, input.recognitionDecision, input.correctedIntent ?? null, now, now);
111
+ });
112
+ const { db } = requireXopcDatabase();
113
+ return getWorkDiscoveryFeedbackFromDb(db, input.runId);
114
+ }
82
115
  function updateWorkDiscoveryRun(id, patch) {
83
116
  const existing = getWorkDiscoveryRun(id);
84
117
  if (!existing) return null;
@@ -95,6 +128,6 @@ function updateWorkDiscoveryRun(id, patch) {
95
128
  return getWorkDiscoveryRun(id);
96
129
  }
97
130
  //#endregion
98
- export { createWorkDiscoveryRun, getWorkDiscoveryOnboardingState, getWorkDiscoveryRun, getWorkDiscoveryRunByIdempotencyKey, setWorkDiscoveryOnboardingState, updateWorkDiscoveryRun };
131
+ export { createWorkDiscoveryRun, getWorkDiscoveryOnboardingState, getWorkDiscoveryRun, getWorkDiscoveryRunByIdempotencyKey, setWorkDiscoveryFeedback, setWorkDiscoveryOnboardingState, updateWorkDiscoveryRun };
99
132
 
100
133
  //# sourceMappingURL=repository.js.map