create-principles-disciple 1.109.0 → 1.111.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/console/dist/server/config/pd-config-store.d.ts +29 -0
- package/console/dist/server/config/pd-config-store.js +55 -0
- package/console/dist/server/feedback/channels.d.ts +36 -0
- package/console/dist/server/feedback/channels.js +89 -0
- package/console/dist/server/feedback/github-adapter.d.ts +44 -0
- package/console/dist/server/feedback/github-adapter.js +125 -0
- package/console/dist/server/feedback/ingest-adapter.d.ts +45 -0
- package/console/dist/server/feedback/ingest-adapter.js +129 -0
- package/console/dist/server/feedback/submit-service.d.ts +44 -0
- package/console/dist/server/feedback/submit-service.js +177 -0
- package/console/dist/server/index.js +23 -7
- package/console/dist/server/models/FeedbackReportConsoleModel.d.ts +8 -0
- package/console/dist/server/models/FeedbackReportConsoleModel.js +45 -0
- package/console/dist/server/routes/feedback-reports.d.ts +24 -0
- package/console/dist/server/routes/feedback-reports.js +151 -0
- package/console/dist/ui/api.d.ts +11 -3
- package/console/dist/ui/api.js +25 -2
- package/console/dist/ui/i18n/en.json +48 -2
- package/console/dist/ui/i18n/zh-CN.json +48 -2
- package/console/dist/ui/pages/ReportProblemValidators.d.ts +30 -0
- package/console/dist/ui/pages/ReportProblemValidators.js +66 -0
- package/console/dist/ui/pages/activation/ActivationPage.js +14 -3
- package/console/dist/ui/pages/pain/PainPage.js +6 -1
- package/console/dist/ui/pages/principles/PrincipleDetailPage.js +4 -1
- package/console/dist/ui/pages/report-problem/ReportProblemPage.js +263 -70
- package/console/dist/ui/utils/validators.d.ts +22 -0
- package/console/dist/ui/utils/validators.js +66 -0
- package/console/dist/web/assets/app.css +42 -0
- package/console/dist/web/assets/app.js +699 -127
- package/console/package.json +4 -4
- package/core/dist/runtime-v2/__tests__/architecture-regression.test.js +4 -0
- package/core/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js +1 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
- package/core/dist/runtime-v2/feedback/__tests__/fingerprint.test.d.ts +2 -0
- package/core/dist/runtime-v2/feedback/__tests__/fingerprint.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/feedback/__tests__/fingerprint.test.js +126 -0
- package/core/dist/runtime-v2/feedback/__tests__/fingerprint.test.js.map +1 -0
- package/core/dist/runtime-v2/feedback/__tests__/slice1-typed-fields.test.d.ts +2 -0
- package/core/dist/runtime-v2/feedback/__tests__/slice1-typed-fields.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/feedback/__tests__/slice1-typed-fields.test.js +278 -0
- package/core/dist/runtime-v2/feedback/__tests__/slice1-typed-fields.test.js.map +1 -0
- package/core/dist/runtime-v2/feedback/create-report.d.ts.map +1 -1
- package/core/dist/runtime-v2/feedback/create-report.js +38 -1
- package/core/dist/runtime-v2/feedback/create-report.js.map +1 -1
- package/core/dist/runtime-v2/feedback/feedback-types.d.ts +60 -0
- package/core/dist/runtime-v2/feedback/feedback-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/feedback/feedback-types.js +58 -0
- package/core/dist/runtime-v2/feedback/feedback-types.js.map +1 -1
- package/core/dist/runtime-v2/feedback/fingerprint.d.ts +31 -0
- package/core/dist/runtime-v2/feedback/fingerprint.d.ts.map +1 -0
- package/core/dist/runtime-v2/feedback/fingerprint.js +51 -0
- package/core/dist/runtime-v2/feedback/fingerprint.js.map +1 -0
- package/core/dist/runtime-v2/feedback/index.d.ts +3 -2
- package/core/dist/runtime-v2/feedback/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/feedback/index.js +2 -1
- package/core/dist/runtime-v2/feedback/index.js.map +1 -1
- package/core/dist/runtime-v2/feedback/privacy-preview.d.ts.map +1 -1
- package/core/dist/runtime-v2/feedback/privacy-preview.js +43 -0
- package/core/dist/runtime-v2/feedback/privacy-preview.js.map +1 -1
- package/core/dist/runtime-v2/feedback/render-markdown.d.ts.map +1 -1
- package/core/dist/runtime-v2/feedback/render-markdown.js +33 -0
- package/core/dist/runtime-v2/feedback/render-markdown.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +2 -2
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +1 -1
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js.map +1 -1
- package/dist/mvp-config.d.ts +22 -1
- package/dist/mvp-config.d.ts.map +1 -1
- package/dist/mvp-config.js +36 -1
- package/dist/mvp-config.js.map +1 -1
- package/package.json +1 -1
- package/plugin/dist/bundle.js +302 -302
- package/plugin/dist/openclaw.plugin.json +1 -18
- package/plugin/openclaw.plugin.json +2 -19
- package/plugin/package.json +3 -3
- package/plugin/scripts/sync-plugin.mjs +34 -33
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// submit-service.ts
|
|
2
|
+
// Orchestrates a feedback submission: load the saved (already-redacted) draft
|
|
3
|
+
// by id, compute its fingerprint, hand it to the chosen adapter, then write
|
|
4
|
+
// the receipt back when the adapter succeeds (spec §8.2).
|
|
5
|
+
//
|
|
6
|
+
// Trust boundaries:
|
|
7
|
+
// - The request body never carries report content — only { channel } (route-level).
|
|
8
|
+
// Everything sent onwards comes from the re-loaded disk draft.
|
|
9
|
+
// - Idempotency: an already-submitted draft returns alreadySubmitted:true and
|
|
10
|
+
// is never resubmitted.
|
|
11
|
+
// - Failure never downgrades the draft's status: any adapter error leaves it
|
|
12
|
+
// 'draft' with a structured reason + nextAction (rc-9).
|
|
13
|
+
// - Write-back failure still returns success + writeBackFailed:true rather than
|
|
14
|
+
// dropping the receipt (spec §12 — "宁重复提示不丢回执").
|
|
15
|
+
import { computeFeedbackFingerprint, } from '@principles/core/runtime-v2/feedback';
|
|
16
|
+
import { submitToIngest } from './ingest-adapter.js';
|
|
17
|
+
import { submitToGithub } from './github-adapter.js';
|
|
18
|
+
/**
|
|
19
|
+
* Submit a saved draft by id through the given channel. `maintainerEmail`
|
|
20
|
+
* (route-level) is accepted separately for parity/future use.
|
|
21
|
+
*/
|
|
22
|
+
export async function submitReport(opts) {
|
|
23
|
+
const { model, reportId, channel, config, deps } = opts;
|
|
24
|
+
// 1. Load the saved draft (already redacted at creation time).
|
|
25
|
+
const loaded = await model.get(reportId);
|
|
26
|
+
const { ok, report, errorCode, error } = loaded;
|
|
27
|
+
if (!ok || !report) {
|
|
28
|
+
if (errorCode === 'NOT_FOUND') {
|
|
29
|
+
return {
|
|
30
|
+
ok: false,
|
|
31
|
+
statusCode: 404,
|
|
32
|
+
error: 'feedback_report_not_found',
|
|
33
|
+
message: `No saved feedback draft with id ${reportId}`,
|
|
34
|
+
nextAction: '从草稿列表选择一个已保存的反馈再提交',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
statusCode: 400,
|
|
40
|
+
error: 'feedback_report_load_failed',
|
|
41
|
+
message: `Failed to load draft: ${error ?? 'unknown'}`,
|
|
42
|
+
nextAction: '检查草稿文件是否可读后重试',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// 2. Idempotency — never resubmit an already-submitted draft.
|
|
46
|
+
if (report.status === 'submitted') {
|
|
47
|
+
return {
|
|
48
|
+
ok: true,
|
|
49
|
+
alreadySubmitted: true,
|
|
50
|
+
status: 'submitted',
|
|
51
|
+
submittedVia: report.submittedVia,
|
|
52
|
+
trackingId: report.trackingId,
|
|
53
|
+
externalUrl: report.externalUrl,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// 3. Channel configuration gate (single-channel disable = remove its config key).
|
|
57
|
+
if (channel === 'ingest' && config.ingestUrl.length === 0) {
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
statusCode: 400,
|
|
61
|
+
error: 'ingest_channel_not_configured',
|
|
62
|
+
message: 'ingest 通道未配置(feedback.ingest_url 为空)',
|
|
63
|
+
nextAction: '在 .pd/config.yaml 配置 feedback.ingest_url,或选择其他通道',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (channel === 'github' && config.githubRepo.length === 0) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
statusCode: 400,
|
|
70
|
+
error: 'github_channel_not_configured',
|
|
71
|
+
message: 'gh 通道未配置(feedback.github_repo 为空)',
|
|
72
|
+
nextAction: '在 .pd/config.yaml 配置 feedback.github_repo,或选择其他通道',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// 4. Deterministic fingerprint for relay-side dedup/clustering.
|
|
76
|
+
const fingerprint = computeFeedbackFingerprint({
|
|
77
|
+
type: report.type,
|
|
78
|
+
title: report.title,
|
|
79
|
+
area: report.area,
|
|
80
|
+
});
|
|
81
|
+
const { area } = report;
|
|
82
|
+
const submittedAt = new Date().toISOString();
|
|
83
|
+
if (channel === 'ingest') {
|
|
84
|
+
const outcome = await submitToIngest({
|
|
85
|
+
url: config.ingestUrl,
|
|
86
|
+
token: config.ingestToken,
|
|
87
|
+
report,
|
|
88
|
+
fingerprint,
|
|
89
|
+
area,
|
|
90
|
+
fetchFn: deps?.fetchFn,
|
|
91
|
+
});
|
|
92
|
+
if (!outcome.ok) {
|
|
93
|
+
// 0 = network/timeout (downstream). Failed → keep draft untouched.
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
statusCode: outcome.status === 0 ? 502 : outcome.status,
|
|
97
|
+
error: 'ingest_submit_failed',
|
|
98
|
+
message: outcome.reason,
|
|
99
|
+
nextAction: outcome.nextAction,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// 5a. Write receipt back; a write-back failure must not drop the success.
|
|
103
|
+
const patch = {
|
|
104
|
+
status: 'submitted',
|
|
105
|
+
submittedAt,
|
|
106
|
+
submittedVia: 'ingest',
|
|
107
|
+
};
|
|
108
|
+
if (outcome.trackingId)
|
|
109
|
+
patch.trackingId = outcome.trackingId;
|
|
110
|
+
if (outcome.issueUrl)
|
|
111
|
+
patch.externalUrl = outcome.issueUrl;
|
|
112
|
+
const wb = await model.update(reportId, patch);
|
|
113
|
+
if (!wb.ok || !wb.report) {
|
|
114
|
+
return {
|
|
115
|
+
ok: true,
|
|
116
|
+
alreadySubmitted: false,
|
|
117
|
+
status: 'submitted',
|
|
118
|
+
submittedVia: 'ingest',
|
|
119
|
+
trackingId: outcome.trackingId,
|
|
120
|
+
externalUrl: outcome.issueUrl,
|
|
121
|
+
writeBackFailed: true,
|
|
122
|
+
nextAction: '提交已完成但状态写回失败,请手动记录回执编号(可在此页面查看)',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
ok: true,
|
|
127
|
+
alreadySubmitted: false,
|
|
128
|
+
status: 'submitted',
|
|
129
|
+
submittedVia: 'ingest',
|
|
130
|
+
trackingId: outcome.trackingId,
|
|
131
|
+
externalUrl: outcome.issueUrl,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
// github
|
|
135
|
+
const outcome = await submitToGithub({
|
|
136
|
+
repo: config.githubRepo,
|
|
137
|
+
proxy: config.githubProxy,
|
|
138
|
+
report,
|
|
139
|
+
execFile: deps?.execFile,
|
|
140
|
+
fs: deps?.fs,
|
|
141
|
+
os: deps?.os,
|
|
142
|
+
path: deps?.path,
|
|
143
|
+
});
|
|
144
|
+
if (!outcome.ok) {
|
|
145
|
+
return {
|
|
146
|
+
ok: false,
|
|
147
|
+
statusCode: 502,
|
|
148
|
+
error: 'github_submit_failed',
|
|
149
|
+
message: outcome.reason,
|
|
150
|
+
nextAction: outcome.nextAction,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const wb = await model.update(reportId, {
|
|
154
|
+
status: 'submitted',
|
|
155
|
+
submittedAt,
|
|
156
|
+
submittedVia: 'github',
|
|
157
|
+
externalUrl: outcome.issueUrl,
|
|
158
|
+
});
|
|
159
|
+
if (!wb.ok || !wb.report) {
|
|
160
|
+
return {
|
|
161
|
+
ok: true,
|
|
162
|
+
alreadySubmitted: false,
|
|
163
|
+
status: 'submitted',
|
|
164
|
+
submittedVia: 'github',
|
|
165
|
+
externalUrl: outcome.issueUrl,
|
|
166
|
+
writeBackFailed: true,
|
|
167
|
+
nextAction: '提交已完成但状态写回失败,请手动记录 issue 链接',
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
ok: true,
|
|
172
|
+
alreadySubmitted: false,
|
|
173
|
+
status: 'submitted',
|
|
174
|
+
submittedVia: 'github',
|
|
175
|
+
externalUrl: outcome.issueUrl,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -2,11 +2,11 @@ import * as http from 'http';
|
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
|
-
import { loadPdConfig, computeFlagsFromLoadResult, getFeedbackMaintainerEmail } from './config/pd-config-store.js';
|
|
5
|
+
import { loadPdConfig, computeFlagsFromLoadResult, getFeedbackMaintainerEmail, getFeedbackChannelConfig, } from './config/pd-config-store.js';
|
|
6
6
|
import { AuthConfig } from './config/AuthConfig.js';
|
|
7
7
|
import { WorkspaceConfigStore } from './config/WorkspaceConfigStore.js';
|
|
8
8
|
import { WorkspaceService } from './models/WorkspaceService.js';
|
|
9
|
-
import { handleFeedbackReportsRoute, disposeFeedbackReportModels } from './routes/feedback-reports.js';
|
|
9
|
+
import { handleFeedbackReportsRoute, handleFeedbackChannelsRoute, disposeFeedbackReportModels, } from './routes/feedback-reports.js';
|
|
10
10
|
import { handleFailedTasksRoute, disposeFailedTasksModels } from './routes/failed-tasks.js';
|
|
11
11
|
import { handleApprovalsRoute, disposeApprovalsModels } from './routes/approvals.js';
|
|
12
12
|
import { handleHealthRoute, disposeHealthModels } from './routes/health.js';
|
|
@@ -216,10 +216,14 @@ async function initServices(workspaceDir, authConfig) {
|
|
|
216
216
|
if (!configResult.ok) {
|
|
217
217
|
console.warn('[pd-console] PD config loading failed (using defaults for feedback channel):', configResult.errors.map(e => e.reason).join('; '));
|
|
218
218
|
}
|
|
219
|
-
// Read feedback.maintainer_email from .pd/config.yaml
|
|
220
|
-
//
|
|
221
|
-
//
|
|
219
|
+
// Read feedback.maintainer_email from .pd/config.yaml. Falls back to a
|
|
220
|
+
// placeholder (maintainer@example.com) when absent; UI/email channel honours
|
|
221
|
+
// that default at the channel-level (not here). Used to build mailto: URLs in
|
|
222
|
+
// feedback reports so the owner can open a pre-filled email directly.
|
|
222
223
|
const maintainerEmail = getFeedbackMaintainerEmail(workspaceDir);
|
|
224
|
+
// Read the feedback submit-channel parameters (ingest_url / ingest_token /
|
|
225
|
+
// github_repo / github_proxy). Presence of a key enables its channel.
|
|
226
|
+
const feedbackChannelConfig = getFeedbackChannelConfig(workspaceDir);
|
|
223
227
|
return {
|
|
224
228
|
workspaceDir,
|
|
225
229
|
authConfig,
|
|
@@ -227,6 +231,7 @@ async function initServices(workspaceDir, authConfig) {
|
|
|
227
231
|
workspaceService,
|
|
228
232
|
feedbackFlags,
|
|
229
233
|
maintainerEmail,
|
|
234
|
+
feedbackChannelConfig,
|
|
230
235
|
};
|
|
231
236
|
}
|
|
232
237
|
async function closeServices() {
|
|
@@ -277,10 +282,21 @@ function handleRequest(services) {
|
|
|
277
282
|
return;
|
|
278
283
|
}
|
|
279
284
|
// ── API routes ──────────────────────────────────────────────────
|
|
280
|
-
// GET
|
|
285
|
+
// GET /api/feedback/submit/channels — submit-ladder probe
|
|
286
|
+
if (urlPath === '/api/feedback/submit/channels') {
|
|
287
|
+
asyncHandler(() => handleFeedbackChannelsRoute(req, res, {
|
|
288
|
+
workspaceDir: services.workspaceDir,
|
|
289
|
+
channelConfig: services.feedbackChannelConfig,
|
|
290
|
+
featureFlags: services.feedbackFlags,
|
|
291
|
+
maintainerEmail: services.maintainerEmail,
|
|
292
|
+
}))(req, res);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
// GET/POST /api/feedback/reports, /api/feedback/reports/:id,
|
|
296
|
+
// POST /api/feedback/reports/:id/submit
|
|
281
297
|
if (urlPath === '/api/feedback/reports' || urlPath.startsWith('/api/feedback/reports/')) {
|
|
282
298
|
const subPath = urlPath.slice('/api/feedback/reports'.length);
|
|
283
|
-
asyncHandler(() => handleFeedbackReportsRoute(req, res, { workspaceDir: services.workspaceDir, subPath, featureFlags: services.feedbackFlags, maintainerEmail: services.maintainerEmail }))(req, res);
|
|
299
|
+
asyncHandler(() => handleFeedbackReportsRoute(req, res, { workspaceDir: services.workspaceDir, subPath, featureFlags: services.feedbackFlags, maintainerEmail: services.maintainerEmail, channelConfig: services.feedbackChannelConfig }))(req, res);
|
|
284
300
|
return;
|
|
285
301
|
}
|
|
286
302
|
// Task 9: GET /api/v1/failed-tasks, /api/v1/failed-tasks/:id
|
|
@@ -4,6 +4,8 @@ export type FeedbackReportDraftSummary = {
|
|
|
4
4
|
createdAt: string;
|
|
5
5
|
type: string;
|
|
6
6
|
title: string;
|
|
7
|
+
/** 分发状态(缺省 draft);用于草稿列表区分 待发/已发(切片3) */
|
|
8
|
+
status?: string;
|
|
7
9
|
};
|
|
8
10
|
export type FeedbackReportListResult = {
|
|
9
11
|
ok: boolean;
|
|
@@ -45,5 +47,11 @@ export declare class FeedbackReportConsoleModel {
|
|
|
45
47
|
* Delete a draft by id. No-op (still ok:true) if the file does not exist.
|
|
46
48
|
*/
|
|
47
49
|
delete(id: string): Promise<FeedbackReportDeleteResult>;
|
|
50
|
+
/**
|
|
51
|
+
* Update a persisted draft by applying a partial patch, then atomic-write.
|
|
52
|
+
* Mirrors `create` (tmp + rename). Fails loud (not_found / read_error /
|
|
53
|
+
* invalid shape) without mutating on error.
|
|
54
|
+
*/
|
|
55
|
+
update(id: string, patch: Partial<FeedbackReport>): Promise<FeedbackReportGetResult>;
|
|
48
56
|
dispose(): void;
|
|
49
57
|
}
|
|
@@ -138,6 +138,7 @@ export class FeedbackReportConsoleModel {
|
|
|
138
138
|
createdAt: obj.createdAt,
|
|
139
139
|
type: obj.type,
|
|
140
140
|
title: obj.title,
|
|
141
|
+
status: typeof obj.status === 'string' ? obj.status : undefined,
|
|
141
142
|
});
|
|
142
143
|
}
|
|
143
144
|
}
|
|
@@ -233,6 +234,50 @@ export class FeedbackReportConsoleModel {
|
|
|
233
234
|
};
|
|
234
235
|
}
|
|
235
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* Update a persisted draft by applying a partial patch, then atomic-write.
|
|
239
|
+
* Mirrors `create` (tmp + rename). Fails loud (not_found / read_error /
|
|
240
|
+
* invalid shape) without mutating on error.
|
|
241
|
+
*/
|
|
242
|
+
async update(id, patch) {
|
|
243
|
+
if (!reportIdValidator(id)) {
|
|
244
|
+
return {
|
|
245
|
+
ok: false,
|
|
246
|
+
error: `Invalid feedback report id: ${JSON.stringify(id)}`,
|
|
247
|
+
errorCode: 'INVALID_ID',
|
|
248
|
+
nextAction: 'pass a safe id matching /^[A-Za-z0-9._-]+$/ with length 1-256',
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
if (patch === null || typeof patch !== 'object' || Array.isArray(patch)) {
|
|
252
|
+
return {
|
|
253
|
+
ok: false,
|
|
254
|
+
error: 'patch must be a plain object with optional FeedbackReport fields',
|
|
255
|
+
nextAction: 'pass a partial FeedbackReport object to update',
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
const loaded = await this.get(id);
|
|
259
|
+
if (!loaded.ok || !loaded.report)
|
|
260
|
+
return loaded;
|
|
261
|
+
const merged = { ...loaded.report, ...patch, id };
|
|
262
|
+
const dir = this.ensureDraftsDir();
|
|
263
|
+
if (!dir.ok)
|
|
264
|
+
return dir;
|
|
265
|
+
const filePath = path.join(this.draftsDir, `${id}.json`);
|
|
266
|
+
try {
|
|
267
|
+
const tmpPath = `${filePath}.${randomUUID()}.tmp`;
|
|
268
|
+
const body = JSON.stringify(merged, null, 2);
|
|
269
|
+
await fs.promises.writeFile(tmpPath, body, { encoding: 'utf8', mode: 0o600 });
|
|
270
|
+
await fs.promises.rename(tmpPath, filePath);
|
|
271
|
+
return { ok: true, report: merged };
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
return {
|
|
275
|
+
ok: false,
|
|
276
|
+
error: `Failed to write feedback report update at ${filePath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
277
|
+
nextAction: 'verify the workspace directory is writable and that disk space is available',
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}
|
|
236
281
|
dispose() {
|
|
237
282
|
// Nothing to dispose; this model holds no connections.
|
|
238
283
|
// (This override satisfies the class-methods-use-this ESLint rule for
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { FeedbackChannelConfig } from '../config/pd-config-store.js';
|
|
3
|
+
import { probeChannels } from '../feedback/channels.js';
|
|
4
|
+
import { type SubmitDeps } from '../feedback/submit-service.js';
|
|
2
5
|
/**
|
|
3
6
|
* Handle requests to `/api/feedback/reports*` for the given workspace.
|
|
4
7
|
* Routes:
|
|
@@ -6,6 +9,7 @@ import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
|
6
9
|
* GET /api/feedback/reports — list draft summaries
|
|
7
10
|
* GET /api/feedback/reports/:id — fetch a single draft
|
|
8
11
|
* DELETE /api/feedback/reports/:id — delete a draft
|
|
12
|
+
* POST /api/feedback/reports/:id/submit — submit a saved draft via a channel
|
|
9
13
|
*/
|
|
10
14
|
export type FeedbackReportsContext = {
|
|
11
15
|
workspaceDir: string;
|
|
@@ -14,6 +18,26 @@ export type FeedbackReportsContext = {
|
|
|
14
18
|
enabled: boolean;
|
|
15
19
|
}>;
|
|
16
20
|
maintainerEmail?: string;
|
|
21
|
+
channelConfig?: FeedbackChannelConfig;
|
|
22
|
+
submitDeps?: SubmitDeps;
|
|
17
23
|
};
|
|
18
24
|
export declare function handleFeedbackReportsRoute(req: IncomingMessage, res: ServerResponse, ctx: FeedbackReportsContext): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Channels context for the submit-ladder probe endpoint.
|
|
27
|
+
*/
|
|
28
|
+
export type FeedbackChannelsContext = {
|
|
29
|
+
workspaceDir: string;
|
|
30
|
+
channelConfig?: FeedbackChannelConfig;
|
|
31
|
+
featureFlags?: Record<string, {
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
maintainerEmail?: string;
|
|
35
|
+
channelDeps?: Parameters<typeof probeChannels>[1];
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* GET /api/feedback/submit/channels — build the channel ladder availability
|
|
39
|
+
* (spec §8.1). When the feedback_channel flag is off, the whole submit area is
|
|
40
|
+
* hidden from the UI; the endpoint still probes but the client drops it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function handleFeedbackChannelsRoute(req: IncomingMessage, res: ServerResponse, ctx: FeedbackChannelsContext): Promise<void>;
|
|
19
43
|
export declare function disposeFeedbackReportModels(): void;
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import { createFeedbackReport, PendingAgentDraftStore, } from '@principles/core/runtime-v2/feedback';
|
|
9
9
|
import { SqliteConnection } from '@principles/core/runtime-v2';
|
|
10
10
|
import { FeedbackReportConsoleModel, } from '../models/FeedbackReportConsoleModel.js';
|
|
11
|
+
import { probeChannels } from '../feedback/channels.js';
|
|
12
|
+
import { submitReport } from '../feedback/submit-service.js';
|
|
11
13
|
import { sendSuccess, sendError, sendNotFound, sendMethodNotAllowed, sendBadRequest, } from '../utils/response.js';
|
|
12
14
|
const models = new Map();
|
|
13
15
|
function getModel(workspaceDir) {
|
|
@@ -67,6 +69,10 @@ async function readJsonBody(req) {
|
|
|
67
69
|
function errorMessage(err) {
|
|
68
70
|
return err instanceof Error ? err.message : String(err);
|
|
69
71
|
}
|
|
72
|
+
// Matches /:id/submit where :id is a safe report id.
|
|
73
|
+
const SUBMIT_SUBPATH = /^\/[A-Za-z0-9._-]+\/submit$/;
|
|
74
|
+
// Matches /:id/mark-sent (manual "marked as sent" for mailto/export channels).
|
|
75
|
+
const MARK_SENT_SUBPATH = /^\/[A-Za-z0-9._-]+\/mark-sent$/;
|
|
70
76
|
export async function handleFeedbackReportsRoute(req, res, ctx) {
|
|
71
77
|
const { workspaceDir, subPath } = ctx;
|
|
72
78
|
const model = getModel(workspaceDir);
|
|
@@ -131,6 +137,127 @@ export async function handleFeedbackReportsRoute(req, res, ctx) {
|
|
|
131
137
|
sendMethodNotAllowed(res);
|
|
132
138
|
return;
|
|
133
139
|
}
|
|
140
|
+
// Submit: POST /api/feedback/reports/:id/submit (feature-flag gated).
|
|
141
|
+
if (SUBMIT_SUBPATH.test(subPath)) {
|
|
142
|
+
if (method !== 'POST') {
|
|
143
|
+
sendMethodNotAllowed(res);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// Feature flag gate — feedback_channel off ⇒ submit endpoint 403 (spec §12).
|
|
147
|
+
if (ctx.featureFlags
|
|
148
|
+
&& Object.hasOwn(ctx.featureFlags, 'feedback_channel')) {
|
|
149
|
+
const feedbackChannel = ctx.featureFlags.feedback_channel;
|
|
150
|
+
if (feedbackChannel && !feedbackChannel.enabled) {
|
|
151
|
+
sendError(res, 403, 'feedback_channel_disabled', 'feedback_channel feature flag is disabled. Enable feedback_channel in .pd/config.yaml to submit feedback.', { nextAction: '在 .pd/config.yaml 将 features.feedback_channel.enabled 改为 true' });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// The report id is everything before the trailing "/submit".
|
|
156
|
+
const reportId = subPath.slice(1, subPath.length - '/submit'.length);
|
|
157
|
+
const bodyResult = await readJsonBody(req);
|
|
158
|
+
if (!bodyResult.ok) {
|
|
159
|
+
sendBadRequest(res, bodyResult.error);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (bodyResult.value === null || typeof bodyResult.value !== 'object' || Array.isArray(bodyResult.value)) {
|
|
163
|
+
sendBadRequest(res, 'request body must be a JSON object with { channel }');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const obj = bodyResult.value;
|
|
167
|
+
const channelRaw = Object.hasOwn(obj, 'channel') ? obj.channel : undefined;
|
|
168
|
+
if (channelRaw !== 'ingest' && channelRaw !== 'github') {
|
|
169
|
+
sendBadRequest(res, 'channel must be one of: "ingest", "github"');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const channel = channelRaw;
|
|
173
|
+
try {
|
|
174
|
+
const result = await submitReport({
|
|
175
|
+
model,
|
|
176
|
+
reportId,
|
|
177
|
+
channel,
|
|
178
|
+
config: ctx.channelConfig ?? { ingestUrl: '', ingestToken: '', githubRepo: '', githubProxy: '' },
|
|
179
|
+
deps: ctx.submitDeps,
|
|
180
|
+
});
|
|
181
|
+
if (!result.ok) {
|
|
182
|
+
sendError(res, result.statusCode, result.error, result.message, { nextAction: result.nextAction });
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
sendSuccess(res, result);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
// rc-9: never silence — structured error + nextAction.
|
|
189
|
+
sendError(res, 500, 'feedback_reports_submit_error', errorMessage(err), {
|
|
190
|
+
nextAction: '稍后重试;或改用导出文件通道发送给维护者',
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
// Manual "mark as sent" for mailto/export channels (spec §11.4): user
|
|
196
|
+
// declares the draft delivered; the console cannot confirm server-side, so
|
|
197
|
+
// this is an honest client-declared status write-back — never a fake ack.
|
|
198
|
+
if (MARK_SENT_SUBPATH.test(subPath)) {
|
|
199
|
+
if (method !== 'POST') {
|
|
200
|
+
sendMethodNotAllowed(res);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (ctx.featureFlags
|
|
204
|
+
&& Object.hasOwn(ctx.featureFlags, 'feedback_channel')) {
|
|
205
|
+
const feedbackChannel = ctx.featureFlags.feedback_channel;
|
|
206
|
+
if (feedbackChannel && !feedbackChannel.enabled) {
|
|
207
|
+
sendError(res, 403, 'feedback_channel_disabled', 'feedback_channel feature flag is disabled. Enable feedback_channel in .pd/config.yaml to mark feedback as sent.', { nextAction: '在 .pd/config.yaml 将 features.feedback_channel.enabled 改为 true' });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const reportId = subPath.slice(1, subPath.length - '/mark-sent'.length);
|
|
212
|
+
const bodyResult = await readJsonBody(req);
|
|
213
|
+
if (!bodyResult.ok) {
|
|
214
|
+
sendBadRequest(res, bodyResult.error);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const viaRaw = bodyResult.value !== null &&
|
|
218
|
+
typeof bodyResult.value === 'object' &&
|
|
219
|
+
!Array.isArray(bodyResult.value)
|
|
220
|
+
? bodyResult.value.via
|
|
221
|
+
: undefined;
|
|
222
|
+
if (viaRaw !== 'email' && viaRaw !== 'file') {
|
|
223
|
+
sendBadRequest(res, 'via must be one of: "email", "file"');
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
const loaded = await model.get(reportId);
|
|
228
|
+
if (!loaded.ok || !loaded.report) {
|
|
229
|
+
sendError(res, loaded.errorCode === 'NOT_FOUND' ? 404 : 400, 'mark_sent_report_not_found', loaded.error ?? 'draft not found', {
|
|
230
|
+
nextAction: '从草稿列表选择一个已保存的反馈再标记',
|
|
231
|
+
});
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// Honesty: do not re-stamp an already-submitted draft (alreadySubmitted).
|
|
235
|
+
if (loaded.report.status === 'submitted') {
|
|
236
|
+
sendSuccess(res, {
|
|
237
|
+
ok: true,
|
|
238
|
+
alreadySubmitted: true,
|
|
239
|
+
status: 'submitted',
|
|
240
|
+
submittedVia: loaded.report.submittedVia,
|
|
241
|
+
trackingId: loaded.report.trackingId,
|
|
242
|
+
externalUrl: loaded.report.externalUrl,
|
|
243
|
+
});
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const submittedAt = new Date().toISOString();
|
|
247
|
+
const wb = await model.update(reportId, { status: 'submitted', submittedAt, submittedVia: viaRaw });
|
|
248
|
+
if (!wb.ok || !wb.report) {
|
|
249
|
+
sendError(res, 500, 'mark_sent_write_back_failed', wb.error ?? 'write-back failed', {
|
|
250
|
+
nextAction: '标记失败,请稍后重试',
|
|
251
|
+
});
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
sendSuccess(res, { ok: true, alreadySubmitted: false, status: 'submitted', submittedVia: viaRaw, submittedAt });
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
sendError(res, 500, 'mark_sent_error', errorMessage(err), { nextAction: '标记失败,请稍后重试' });
|
|
258
|
+
}
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
134
261
|
// Single draft: /:id
|
|
135
262
|
const id = subPath.replace(/^\//, '');
|
|
136
263
|
if (id.length === 0) {
|
|
@@ -176,6 +303,30 @@ export async function handleFeedbackReportsRoute(req, res, ctx) {
|
|
|
176
303
|
}
|
|
177
304
|
sendMethodNotAllowed(res);
|
|
178
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* GET /api/feedback/submit/channels — build the channel ladder availability
|
|
308
|
+
* (spec §8.1). When the feedback_channel flag is off, the whole submit area is
|
|
309
|
+
* hidden from the UI; the endpoint still probes but the client drops it.
|
|
310
|
+
*/
|
|
311
|
+
export async function handleFeedbackChannelsRoute(req, res, ctx) {
|
|
312
|
+
const method = req.method ?? 'GET';
|
|
313
|
+
if (method !== 'GET') {
|
|
314
|
+
sendMethodNotAllowed(res);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
try {
|
|
318
|
+
const config = ctx.channelConfig ?? { ingestUrl: '', ingestToken: '', githubRepo: '', githubProxy: '' };
|
|
319
|
+
const result = await probeChannels(config, ctx.channelDeps ?? {}, ctx.maintainerEmail ?? '');
|
|
320
|
+
// Never leak the ingest token into a client-facing payload (probeChannels already omits it).
|
|
321
|
+
sendSuccess(res, { channels: result.channels });
|
|
322
|
+
}
|
|
323
|
+
catch (err) {
|
|
324
|
+
// rc-9: structured failure with nextAction; the client hides submit area.
|
|
325
|
+
sendError(res, 500, 'feedback_channels_probe_error', errorMessage(err), {
|
|
326
|
+
nextAction: '稍后重试;或直接使用导出文件通道',
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
179
330
|
export function disposeFeedbackReportModels() {
|
|
180
331
|
for (const model of models.values()) {
|
|
181
332
|
model.dispose();
|
package/console/dist/ui/api.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ApiResponse } from "../types.js";
|
|
2
2
|
import { listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm } from "./utils/signal-keywords-api.js";
|
|
3
|
-
import type { FeedbackReportData, FeedbackDraftSummaryData, FeedbackDraftEnvelopeData, DeleteEnvelopeData, WorkspaceEntryData, RemovedEnvelopeData, SyncResultData, ConfigSummaryData, ConfigCatalogData, AgentBindingUpdateData, DefaultRuntimeUpdateData, RuntimeProfileMutationData, ReadinessCheckData, FeatureFlagUpdateData, OutputLanguageData, GovernanceQueueData, ActivationsData, DisableActivationData, LifecycleMetricsData, UpdateStatusData, UpdateHistoryData, ApplyUpdateResultData, RollbackResultData, ApprovalRecordData, PrinciplesListData, ApprovalsGroupedData, EvidenceChainData, TrajectoryData, IntentSummaryData, IntentDecisionRecordData, IntentDecisionResultData, IntentDecisionSummaryData, FollowUpResponseData, IntentInitResultData, IntentSaveResultData, IntentRawContentData, IntentVersionData } from "./utils/validators.js";
|
|
3
|
+
import type { FeedbackReportData, FeedbackDraftSummaryData, FeedbackDraftEnvelopeData, FeedbackChannelsData, FeedbackSubmitResultData, DeleteEnvelopeData, WorkspaceEntryData, RemovedEnvelopeData, SyncResultData, ConfigSummaryData, ConfigCatalogData, AgentBindingUpdateData, DefaultRuntimeUpdateData, RuntimeProfileMutationData, ReadinessCheckData, FeatureFlagUpdateData, OutputLanguageData, GovernanceQueueData, ActivationsData, DisableActivationData, LifecycleMetricsData, UpdateStatusData, UpdateHistoryData, ApplyUpdateResultData, RollbackResultData, ApprovalRecordData, PrinciplesListData, ApprovalsGroupedData, EvidenceChainData, TrajectoryData, IntentSummaryData, IntentDecisionRecordData, IntentDecisionResultData, IntentDecisionSummaryData, FollowUpResponseData, IntentInitResultData, IntentSaveResultData, IntentRawContentData, IntentVersionData } from "./utils/validators.js";
|
|
4
4
|
declare function getToken(): string | null;
|
|
5
5
|
declare function setToken(token: string): void;
|
|
6
6
|
declare function clearToken(): void;
|
|
@@ -37,6 +37,14 @@ declare function listFeedbackReports(): Promise<ApiResponse<{
|
|
|
37
37
|
}>>;
|
|
38
38
|
declare function getFeedbackReport(id: string): Promise<ApiResponse<FeedbackDraftEnvelopeData>>;
|
|
39
39
|
declare function deleteFeedbackReport(id: string): Promise<ApiResponse<DeleteEnvelopeData>>;
|
|
40
|
+
declare function fetchFeedbackChannels(): Promise<ApiResponse<FeedbackChannelsData>>;
|
|
41
|
+
declare function submitFeedbackReport(id: string, channel: 'ingest' | 'github'): Promise<ApiResponse<FeedbackSubmitResultData>>;
|
|
42
|
+
/**
|
|
43
|
+
* Manually mark a draft as sent (mailto/export channels, spec §11.4). The
|
|
44
|
+
* console cannot server-confirm delivery for these channels, so this is an
|
|
45
|
+
* honest user-declared status write-back — never a fake acknowledgement.
|
|
46
|
+
*/
|
|
47
|
+
declare function markFeedbackReportSent(id: string, via: 'email' | 'file'): Promise<ApiResponse<FeedbackSubmitResultData>>;
|
|
40
48
|
declare function fetchConfigSummary(): Promise<ApiResponse<ConfigSummaryData>>;
|
|
41
49
|
declare function fetchConfigCatalog(): Promise<ApiResponse<ConfigCatalogData>>;
|
|
42
50
|
declare function updateAgentBinding(agentName: string, runtimeProfile: string, enabled: boolean): Promise<ApiResponse<AgentBindingUpdateData>>;
|
|
@@ -148,8 +156,8 @@ export interface FollowUpPayload {
|
|
|
148
156
|
candidateId?: string;
|
|
149
157
|
}
|
|
150
158
|
declare function dispatchFollowUp(decisionId: string, payload: FollowUpPayload): Promise<ApiResponse<FollowUpResponseData>>;
|
|
151
|
-
export { request, listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm, getToken, setToken, clearToken, checkAuth, approveApproval, rejectApproval, editApproval, fetchPrinciples, fetchPrincipleDetail, fetchPrincipleTrajectory, archivePrinciple, unarchivePrinciple, createFeedbackReport, listFeedbackReports, getFeedbackReport, deleteFeedbackReport, fetchConfigSummary, fetchConfigCatalog, updateAgentBinding, updateDefaultRuntime, createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, fetchAgentReadiness, patchFeatureFlag, fetchOutputLanguage, updateOutputLanguage, fetchWorkspaces, addWorkspace, removeWorkspace, syncWorkspace, fetchGovernanceQueue, fetchApprovalsGrouped, fetchAllActivations, disableActivation, fetchLifecycleMetrics, fetchUpdateStatus, fetchUpdateHistory, applyUpdate, applyFullUpdate, rollbackUpdate, fetchEvidenceChain, fetchIntentSummary, fetchIntentContent, createIntentTemplate, saveIntentContent, fetchIntentVersions, recordIntentDecision, listIntentDecisionsByPainId, listIntentDecisionsByTaskId, fetchIntentDecisionSummary, dispatchFollowUp, };
|
|
152
|
-
export type { FeedbackReportData, FeedbackDraftSummaryData, FeedbackDraftEnvelopeData, DeleteEnvelopeData, WorkspaceEntryData, RemovedEnvelopeData, SyncResultData, ConfigSummaryData, ConfigCatalogData, AgentBindingUpdateData, DefaultRuntimeUpdateData, RuntimeProfileMutationData, ReadinessCheckData, FeatureFlagUpdateData, OutputLanguageData, GovernanceQueueData, ActivationsData, DisableActivationData, LifecycleMetricsData, UpdateStatusData, UpdateHistoryData, ApplyUpdateResultData, RollbackResultData, ApprovalRecordData, PrinciplesListData, ApprovalsGroupedData, EvidenceChainData, EvidenceChainRecordData, EvidenceChainStateData, TrajectoryData, TrajectoryStageData, IntentSummaryData, IntentSectionsData, IntentDocWarningData, IntentDecisionRecordData, IntentDecisionResultData, IntentDecisionSummaryData, FollowUpResponseData, LinkCandidateFollowUpData, GuideRulehostFollowUpData, GeneratePatchProposalFollowUpData, IntentVersionEntry, IntentVersionData, } from "./utils/validators.js";
|
|
159
|
+
export { request, listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm, getToken, setToken, clearToken, checkAuth, approveApproval, rejectApproval, editApproval, fetchPrinciples, fetchPrincipleDetail, fetchPrincipleTrajectory, archivePrinciple, unarchivePrinciple, createFeedbackReport, listFeedbackReports, getFeedbackReport, deleteFeedbackReport, fetchFeedbackChannels, submitFeedbackReport, markFeedbackReportSent, fetchConfigSummary, fetchConfigCatalog, updateAgentBinding, updateDefaultRuntime, createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, fetchAgentReadiness, patchFeatureFlag, fetchOutputLanguage, updateOutputLanguage, fetchWorkspaces, addWorkspace, removeWorkspace, syncWorkspace, fetchGovernanceQueue, fetchApprovalsGrouped, fetchAllActivations, disableActivation, fetchLifecycleMetrics, fetchUpdateStatus, fetchUpdateHistory, applyUpdate, applyFullUpdate, rollbackUpdate, fetchEvidenceChain, fetchIntentSummary, fetchIntentContent, createIntentTemplate, saveIntentContent, fetchIntentVersions, recordIntentDecision, listIntentDecisionsByPainId, listIntentDecisionsByTaskId, fetchIntentDecisionSummary, dispatchFollowUp, };
|
|
160
|
+
export type { FeedbackReportData, FeedbackDraftSummaryData, FeedbackDraftEnvelopeData, FeedbackChannelsData, FeedbackSubmitResultData, DeleteEnvelopeData, WorkspaceEntryData, RemovedEnvelopeData, SyncResultData, ConfigSummaryData, ConfigCatalogData, AgentBindingUpdateData, DefaultRuntimeUpdateData, RuntimeProfileMutationData, ReadinessCheckData, FeatureFlagUpdateData, OutputLanguageData, GovernanceQueueData, ActivationsData, DisableActivationData, LifecycleMetricsData, UpdateStatusData, UpdateHistoryData, ApplyUpdateResultData, RollbackResultData, ApprovalRecordData, PrinciplesListData, ApprovalsGroupedData, EvidenceChainData, EvidenceChainRecordData, EvidenceChainStateData, TrajectoryData, TrajectoryStageData, IntentSummaryData, IntentSectionsData, IntentDocWarningData, IntentDecisionRecordData, IntentDecisionResultData, IntentDecisionSummaryData, FollowUpResponseData, LinkCandidateFollowUpData, GuideRulehostFollowUpData, GeneratePatchProposalFollowUpData, IntentVersionEntry, IntentVersionData, } from "./utils/validators.js";
|
|
153
161
|
export type { SignalKeyword, PendingSignalTerm } from "./utils/signal-keywords-types.js";
|
|
154
162
|
export type { ActivationRecordData as ActivationRecord } from "./utils/validators.js";
|
|
155
163
|
export type { ApprovalRecordData as ApprovalRecord } from "./utils/validators.js";
|
package/console/dist/ui/api.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm, } from "./utils/signal-keywords-api.js";
|
|
2
|
-
import { validateErrorResponse, validateHeaders, validateFeedbackReport, validateFeedbackDraftsList, validateFeedbackDraftEnvelope, validateDeleteEnvelope, validateWorkspaceEntry, validateWorkspaceList, validateRemovedEnvelope, validateSyncResult, validateConfigSummary, validateConfigCatalog, validateAgentBindingUpdate, validateDefaultRuntimeUpdate, validateRuntimeProfileMutation, validateReadinessCheck, validateFeatureFlagUpdate, validateOutputLanguage, validateGovernanceQueue, validateActivations, validateDisableActivation, validateLifecycleMetrics, validateUpdateStatus, validateUpdateHistory, validateApplyUpdateResult, validateRollbackResult, validateApprovalRecordDirect, validatePrinciplesList, validateApprovalsGrouped, validateEvidenceChain, validateTrajectoryData, validateIntentSummary, validateIntentDecisionList, validateIntentDecisionResult, validateIntentDecisionSummary, validateFollowUpResponse, validateIntentInitResult, validateIntentSaveResult, validateIntentRawContent, validateIntentVersions, } from "./utils/validators.js";
|
|
2
|
+
import { validateErrorResponse, validateHeaders, validateFeedbackReport, validateFeedbackDraftsList, validateFeedbackDraftEnvelope, validateFeedbackChannels, validateFeedbackSubmitResult, validateDeleteEnvelope, validateWorkspaceEntry, validateWorkspaceList, validateRemovedEnvelope, validateSyncResult, validateConfigSummary, validateConfigCatalog, validateAgentBindingUpdate, validateDefaultRuntimeUpdate, validateRuntimeProfileMutation, validateReadinessCheck, validateFeatureFlagUpdate, validateOutputLanguage, validateGovernanceQueue, validateActivations, validateDisableActivation, validateLifecycleMetrics, validateUpdateStatus, validateUpdateHistory, validateApplyUpdateResult, validateRollbackResult, validateApprovalRecordDirect, validatePrinciplesList, validateApprovalsGrouped, validateEvidenceChain, validateTrajectoryData, validateIntentSummary, validateIntentDecisionList, validateIntentDecisionResult, validateIntentDecisionSummary, validateFollowUpResponse, validateIntentInitResult, validateIntentSaveResult, validateIntentRawContent, validateIntentVersions, } from "./utils/validators.js";
|
|
3
3
|
// ── Auth ──────────────────────────────────────────────────────────────────────
|
|
4
4
|
function getToken() {
|
|
5
5
|
return sessionStorage.getItem("pd_token");
|
|
@@ -182,6 +182,27 @@ async function deleteFeedbackReport(id) {
|
|
|
182
182
|
method: 'DELETE',
|
|
183
183
|
}, validateDeleteEnvelope);
|
|
184
184
|
}
|
|
185
|
+
// ── Feedback submit ladder (Slice 3, spec §8) ─────────────────────────────────
|
|
186
|
+
async function fetchFeedbackChannels() {
|
|
187
|
+
return request('/api/feedback/submit/channels', undefined, validateFeedbackChannels);
|
|
188
|
+
}
|
|
189
|
+
async function submitFeedbackReport(id, channel) {
|
|
190
|
+
return request('/api/feedback/reports/' + encodeURIComponent(id) + '/submit', {
|
|
191
|
+
method: 'POST',
|
|
192
|
+
body: JSON.stringify({ channel }),
|
|
193
|
+
}, validateFeedbackSubmitResult);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Manually mark a draft as sent (mailto/export channels, spec §11.4). The
|
|
197
|
+
* console cannot server-confirm delivery for these channels, so this is an
|
|
198
|
+
* honest user-declared status write-back — never a fake acknowledgement.
|
|
199
|
+
*/
|
|
200
|
+
async function markFeedbackReportSent(id, via) {
|
|
201
|
+
return request('/api/feedback/reports/' + encodeURIComponent(id) + '/mark-sent', {
|
|
202
|
+
method: 'POST',
|
|
203
|
+
body: JSON.stringify({ via }),
|
|
204
|
+
}, validateFeedbackSubmitResult);
|
|
205
|
+
}
|
|
185
206
|
// ── Config / Control Center API (PRI-303, PRI-309) ───────────────────────────
|
|
186
207
|
async function fetchConfigSummary() {
|
|
187
208
|
return request('/api/v1/config/summary', undefined, validateConfigSummary);
|
|
@@ -381,4 +402,6 @@ export { request,
|
|
|
381
402
|
// signal-keywords (UI stubs, see signal-keywords-api.ts)
|
|
382
403
|
listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm,
|
|
383
404
|
// auth
|
|
384
|
-
getToken, setToken, clearToken, checkAuth, approveApproval, rejectApproval, editApproval, fetchPrinciples, fetchPrincipleDetail, fetchPrincipleTrajectory, archivePrinciple, unarchivePrinciple, createFeedbackReport, listFeedbackReports, getFeedbackReport, deleteFeedbackReport,
|
|
405
|
+
getToken, setToken, clearToken, checkAuth, approveApproval, rejectApproval, editApproval, fetchPrinciples, fetchPrincipleDetail, fetchPrincipleTrajectory, archivePrinciple, unarchivePrinciple, createFeedbackReport, listFeedbackReports, getFeedbackReport, deleteFeedbackReport,
|
|
406
|
+
// Feedback submit ladder (Slice 3, spec §8)
|
|
407
|
+
fetchFeedbackChannels, submitFeedbackReport, markFeedbackReportSent, fetchConfigSummary, fetchConfigCatalog, updateAgentBinding, updateDefaultRuntime, createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, fetchAgentReadiness, patchFeatureFlag, fetchOutputLanguage, updateOutputLanguage, fetchWorkspaces, addWorkspace, removeWorkspace, syncWorkspace, fetchGovernanceQueue, fetchApprovalsGrouped, fetchAllActivations, disableActivation, fetchLifecycleMetrics, fetchUpdateStatus, fetchUpdateHistory, applyUpdate, applyFullUpdate, rollbackUpdate, fetchEvidenceChain, fetchIntentSummary, fetchIntentContent, createIntentTemplate, saveIntentContent, fetchIntentVersions, recordIntentDecision, listIntentDecisionsByPainId, listIntentDecisionsByTaskId, fetchIntentDecisionSummary, dispatchFollowUp, };
|