@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.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{activity-store-DXeSSlw1.js → activity-store-B4s0ipx2.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-CFdJKSjY.js → agent-avatar-dicebear-BSccACje.js} +1 -1
- package/dist/gateway/static/root/assets/agents-DZDOcgz5.js +2 -0
- package/dist/gateway/static/root/assets/{api-Bs69B5zA.js → api-BMdUsNCV.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-ag1ViXT_.js → app-management-settings-panel-DrLGhYkI.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-C6iMpwOM.js → appearance-settings-BNasnY9D.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-Cmr-Hvm9.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-Dwi3mIBy.js → archive-plugin-BQ17IK9x.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-BaCDoq40.js → attachment-load-HXvgWHXu.js} +1 -1
- package/dist/gateway/static/root/assets/{automation-suggestion-card-CwqtFgnw.js → automation-suggestion-card-CjIWpsch.js} +1 -1
- package/dist/gateway/static/root/assets/automations-page-TWgUAI8t.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-DoAYhsxa.js → binary-plugins-DifWJzNZ.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-Bw72pO7S.js → block-editor-DtRzIkPU.js} +4 -4
- package/dist/gateway/static/root/assets/browser-settings-page-DBsFItW_.js +8 -0
- package/dist/gateway/static/root/assets/{capability-presets-api-vdbAR65Q.js → capability-presets-api-DazKjWW1.js} +1 -1
- package/dist/gateway/static/root/assets/{capability-presets-settings-panel-BAsKbJgm.js → capability-presets-settings-panel-Cvrw4fD1.js} +2 -2
- package/dist/gateway/static/root/assets/{channel-recipient-api-rTG26jpV.js → channel-recipient-api-Bea6if_6.js} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-BBkzrBYy.js +1 -0
- package/dist/gateway/static/root/assets/channels-status-swr-CuU6ez3y.js +1 -0
- package/dist/gateway/static/root/assets/{connector-logo-DIMP4NWY.js → connector-logo-BjbYbJjQ.js} +1 -1
- package/dist/gateway/static/root/assets/connectors-page-DLsZCAw7.js +2 -0
- package/dist/gateway/static/root/assets/{desktop-pet-kQq2e0BN.js → desktop-pet-C6V-PHwi.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-B6vtozMY.js → desktop-pet-settings-5Fcz47XQ.js} +1 -1
- package/dist/gateway/static/root/assets/{directory-picker-path-field-Dn5-e3v4.js → directory-picker-path-field-C8zZNUzC.js} +1 -1
- package/dist/gateway/static/root/assets/{dreaming-settings-BzwIujPG.js → dreaming-settings-gJSw7Adk.js} +3 -3
- package/dist/gateway/static/root/assets/{extension-debug-page-D_e_hsEy.js → extension-debug-page-DFE5ADJ0.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-D15CWHBd.js → extension-page-CfR-aW8G.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-D4P0n1om.js → extension-settings-page-CFIEbs2L.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-api-B4qZctjG.js → gateway-config-api-B6M_ra8U.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-settings-BrxzyVW2.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-BpgMunEb.js → gateway-startup-retry-DNA3OOzV.js} +1 -1
- package/dist/gateway/static/root/assets/{goal-create-dialog-DrVHKXHq.js → goal-create-dialog-DRVRLc5Z.js} +1 -1
- package/dist/gateway/static/root/assets/goal-detail-page-8F7NElzP.js +4 -0
- package/dist/gateway/static/root/assets/{goals-config-api-B6YJB_ct.js → goals-config-api-D88opgcs.js} +1 -1
- package/dist/gateway/static/root/assets/goals-page-BaoLiV8G.js +2 -0
- package/dist/gateway/static/root/assets/{goals-settings-BROWCIcL.js → goals-settings-D07UL8Jo.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-CoCwIOIg.js → heartbeat-settings-YCSNOcXC.js} +1 -1
- package/dist/gateway/static/root/assets/{index-CxkNzw3e.js → index-CH7XN6KY.js} +43 -43
- package/dist/gateway/static/root/assets/index-Dfnl9tp_.css +1 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Bm_AIYQ7.js → keyboard-shortcuts-settings-BHnh5OoY.js} +1 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-Boe8n4zH.js +2 -0
- package/dist/gateway/static/root/assets/local-apps-page-CKvtLzsF.js +1 -0
- package/dist/gateway/static/root/assets/{locale-store-Bp8joZck.js → locale-store-BneNAR8R.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-Cb4Uptct.js +7 -0
- package/dist/gateway/static/root/assets/{markdown-split-Dy7QpaAz.js → markdown-split-BVSFG99p.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-3Lxliy9D.js → media-plugins-DdOjIVk3.js} +1 -1
- package/dist/gateway/static/root/assets/{messages-CQGpD-dC.js → messages-t1yPaVCt.js} +5 -5
- package/dist/gateway/static/root/assets/models-hub-panel-CNiUKZUV.js +2 -0
- package/dist/gateway/static/root/assets/{notes-page-BJtT4vSf.js → notes-page-ZbLR3awn.js} +1 -1
- package/dist/gateway/static/root/assets/notes-workbench-C9__VkD6.js +4 -0
- package/dist/gateway/static/root/assets/{page-tabs-DKB6RDre.js → page-tabs-DMlQERNg.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-Br_YhYHp.js → preview-open-alternatives-CbiJRPxK.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-CZhuB5kO.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-BOq4WVdR.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-BxGq0ebQ.js +1 -0
- package/dist/gateway/static/root/assets/{save-bar-controls-BgIE9BBh.js → save-bar-controls-BZTj3hxl.js} +1 -1
- package/dist/gateway/static/root/assets/{schedule-field-BXDP-oMz.js → schedule-field-CxYDObjT.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-Dhv-SmjG.js → schema-form-CBbXgpKQ.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-B6gSU_Au.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-DwuBL834.js +1 -0
- package/dist/gateway/static/root/assets/{settings-collapsible-section-CUTExnlK.js → settings-collapsible-section-BySKZ8rp.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-CmnDhsf0.js → settings-form-section-CjINay6t.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-loading-skeleton-B79dq-XY.js → settings-loading-skeleton-vitElc-n.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-Db8uAv3X.js → settings-page-BNrVwuL7.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-layout-DaFwstQw.js → settings-page-layout-HdlQOOqp.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-BJeA4mmk.js +1 -0
- package/dist/gateway/static/root/assets/{share-preview-page-Bi00uXzV.js → share-preview-page-DC-eQONB.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-DB2KYA2_.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-DQtZiejw.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-Pm0fNe_p.js +2 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-Cxaph7hW.js → system-settings-panel-CVa90q2f.js} +1 -1
- package/dist/gateway/static/root/assets/{theme-store-eCTN1RpG.js → theme-store-D1cOpgsy.js} +1 -1
- package/dist/gateway/static/root/assets/{typed-models-lib-CcZ_tyFY.js → typed-models-lib-DIKF9vpz.js} +1 -1
- package/dist/gateway/static/root/assets/{use-channel-catalog-C4PWbNoi.js → use-channel-catalog-DnvpI0Ys.js} +1 -1
- package/dist/gateway/static/root/assets/user-context-page-DnLqQyRG.js +1 -0
- package/dist/gateway/static/root/assets/{user-profile-fields-editor-CPOO9Jz5.js → user-profile-fields-editor-y8gTmkPX.js} +1 -1
- package/dist/gateway/static/root/assets/{user-profile-settings-panel-C8cgtSF7.js → user-profile-settings-panel-C3pvqe28.js} +1 -1
- package/dist/gateway/static/root/assets/{utils-CiZK2KwH.js → utils-BK2vwu_3.js} +1 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-BiFIIWC4.js +1 -0
- package/dist/gateway/static/root/assets/work-discovery-page-D2-_bC0J.js +1 -0
- package/dist/gateway/static/root/assets/work-item-detail-page-B0FPG4eJ.js +1 -0
- package/dist/gateway/static/root/assets/work-page-BDeVxts_.js +1 -0
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-BPzS67fU.js +3 -0
- package/dist/gateway/static/root/assets/workflows-page-xPhmrRLs.js +11 -0
- package/dist/gateway/static/root/index.html +7 -7
- package/dist/package.js +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/gateway/hono/routes/work-discovery.js +32 -0
- package/dist/src/gateway/hono/routes/work-discovery.js.map +1 -1
- package/dist/src/storage/sqlite/migrations/046_work_discovery_feedback.sql +13 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/work-discovery/analyzer.js +23 -4
- package/dist/src/work-discovery/analyzer.js.map +1 -1
- package/dist/src/work-discovery/index.js +2 -2
- package/dist/src/work-discovery/probe.d.ts +6 -0
- package/dist/src/work-discovery/probe.js +12 -1
- package/dist/src/work-discovery/probe.js.map +1 -1
- package/dist/src/work-discovery/repository.d.ts +7 -1
- package/dist/src/work-discovery/repository.js +35 -2
- package/dist/src/work-discovery/repository.js.map +1 -1
- package/dist/src/work-discovery/service.d.ts +6 -1
- package/dist/src/work-discovery/service.js +64 -5
- package/dist/src/work-discovery/service.js.map +1 -1
- package/dist/src/work-discovery/types.d.ts +21 -0
- package/dist/src/workflows/service/workflow-run-service.js +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/agents-DKjbRpbA.js +0 -2
- package/dist/gateway/static/root/assets/apps-page-CpX3lBFF.js +0 -1
- package/dist/gateway/static/root/assets/automations-page-DlupOxFQ.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-BwLoUBcZ.js +0 -8
- package/dist/gateway/static/root/assets/channels-settings-CxFWy0cE.js +0 -1
- package/dist/gateway/static/root/assets/channels-status-swr-OKlqTtKJ.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-BjQQwX62.js +0 -2
- package/dist/gateway/static/root/assets/gateway-settings-BvndV7TP.js +0 -1
- package/dist/gateway/static/root/assets/goal-detail-page-PHlCwv68.js +0 -4
- package/dist/gateway/static/root/assets/goals-page-Hg9uyO6e.js +0 -2
- package/dist/gateway/static/root/assets/index-DjCSOyaN.css +0 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-BJWFcxwG.js +0 -2
- package/dist/gateway/static/root/assets/local-apps-page-BsiZMrEf.js +0 -1
- package/dist/gateway/static/root/assets/logs-page-DOmK61ey.js +0 -7
- package/dist/gateway/static/root/assets/models-hub-panel-DGCZom4o.js +0 -2
- package/dist/gateway/static/root/assets/notes-workbench-B_bN7cOR.js +0 -4
- package/dist/gateway/static/root/assets/project-detail-page-Dq8JouJj.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-DvfL7fSe.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-DqwMXSXP.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-B_mOZkQk.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-BHGmH6YS.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-BUSEj0KK.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-DzsTYfuX.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-DSsdU081.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-DeB1tQZL.js +0 -2
- package/dist/gateway/static/root/assets/user-context-page-QTVMx_hG.js +0 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-b-YZtvUu.js +0 -1
- package/dist/gateway/static/root/assets/work-discovery-page-4zVDOl2G.js +0 -1
- package/dist/gateway/static/root/assets/work-item-detail-page-CHe2tfkc.js +0 -1
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-8SLCYeXE.js +0 -3
- 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-
|
|
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-
|
|
28
|
-
<link rel="modulepreload" crossorigin href="/assets/locale-store-
|
|
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-
|
|
32
|
-
<link rel="modulepreload" crossorigin href="/assets/activity-store-
|
|
33
|
-
<link rel="modulepreload" crossorigin href="/assets/theme-store-
|
|
34
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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
|
@@ -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
|
|
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":"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"}
|
|
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 =
|
|
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 =
|
|
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 ??
|
|
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 ??
|
|
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 =
|
|
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 =
|
|
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:
|
|
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,
|
|
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":"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"}
|
|
@@ -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
|
-
|
|
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
|