@h-ai/feedback 0.1.0-alpha.74 → 0.1.0-alpha.75
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/README.en.md +14 -4
- package/README.ja.md +14 -4
- package/README.md +14 -4
- package/dist/bundle/index.d.ts +2 -0
- package/dist/bundle/index.js +1 -1
- package/dist/{chunk-UIVT267C.js → chunk-7W47WZXY.js} +10 -6
- package/dist/chunk-7W47WZXY.js.map +1 -0
- package/dist/{chunk-5QWKURWU.js → chunk-KGMLTOMM.js} +5 -3
- package/dist/chunk-KGMLTOMM.js.map +1 -0
- package/dist/client/index.d.ts +12 -2
- package/dist/client/index.js +110 -17
- package/dist/client/index.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +276 -57
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/dist/chunk-5QWKURWU.js.map +0 -1
- package/dist/chunk-UIVT267C.js.map +0 -1
package/README.en.md
CHANGED
|
@@ -54,7 +54,7 @@ Browser and server text redaction share rules for common credential keys, JSON/e
|
|
|
54
54
|
|
|
55
55
|
`text/plain` attachments are redacted in full within the 5 MiB file limit, preserving safe content and trailing log entries without structured evidence's per-field truncation. Oversized original files are rejected before reading, and redacted output remains subject to the same attachment byte limit. JSON attachments and structured evidence retain field length, depth and count limits and may be trimmed; use plain text for complete logs. The same rules apply to pending attachments and files added to a local export after the first submission envelope is frozen.
|
|
56
56
|
|
|
57
|
-
The complete client exposes `init(config)`, `prepare/captureScreenshot(attachmentId?)/submit/flush/record/getDraft/getView/annotate/addFiles/exportDraft/saveDraft/close`. Connect form `onfileschange` to `addFiles` and refresh draft attachment previews so new images can be marked, masked or removed before submission. Use `data-feedback-action` for key interactions, `data-feedback-private` for sensitive content and `data-feedback-ignore` for the feedback UI itself. The first transmitted envelope is frozen; changing input during a retry returns a conflict and preserves the input for local export.
|
|
57
|
+
The complete client exposes `init(config)`, `prepare/captureScreenshot(attachmentId?)/submit/flush/record/getDraft/getView/annotate/addFiles/exportDraft/saveDraft/pause/close`. Connect form `onfileschange` to `addFiles` and refresh draft attachment previews so new images can be marked, masked or removed before submission. Use `data-feedback-action` for key interactions, `data-feedback-private` for sensitive content and `data-feedback-ignore` for the feedback UI itself. The first transmitted envelope is frozen; changing input during a retry returns a conflict and preserves the input for local export.
|
|
58
58
|
|
|
59
59
|
`addFiles` adds each batch atomically: all files must pass validation before merging. Any failure preserves existing attachments and missing markers without saving a partial batch. Successful concurrent batches preserve each other's files, with at most 20 merged attachments. If the draft changes or the first envelope freezes during reading, it returns `CONFLICT` without adding hidden uploads outside that envelope.
|
|
60
60
|
|
|
@@ -66,9 +66,11 @@ Concurrent `init` returns `INIT_IN_PROGRESS`; `close` drains bounded screenshots
|
|
|
66
66
|
|
|
67
67
|
The SDK owns the draft and updates it through methods such as `annotate` and `addFiles`. Svelte hosts should keep this reference in `$state.raw<FeedbackDraft>()` and refresh attachment views from `getDraft()` after successful operations. A deep `$state` proxy can retain cached image bytes after the SDK replaces them on the original draft.
|
|
68
68
|
|
|
69
|
-
`maxTotalBytes` defaults to 32 MiB (4–40 MiB). `feedback.budget` derives individual/total attachment, duplicate evidence and Base64/encrypted transport budgets from it. Defaults are 5 MiB per file, 22 MiB for attachments, 4 MiB/2000 evidence records, plus 2 MiB for history, metadata and the viewer. Submission validates declared totals; evidence updates enforce quotas atomically; processing history is limited to 256 KiB. The authenticated `reports.policy({})` supplies effective limits to the client and form.
|
|
69
|
+
`maxTotalBytes` defaults to 32 MiB (4–40 MiB). `feedback.budget` derives individual/total attachment, duplicate evidence and Base64/encrypted transport budgets from it. Defaults are 5 MiB per file, 22 MiB for attachments, 4 MiB/2000 evidence records, plus 2 MiB for history, metadata and the viewer. Submission validates declared totals; evidence updates enforce quotas atomically; processing history is limited to 256 KiB. The authenticated `reports.policy({})` supplies effective limits to the client and form.
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
Ordinary routes retain `transport.maxPlaintextBytes` (auto/omitted: 1 MiB). Declare Feedback report intake, evidence, attachments and ZIP download capacity separately using `routePlaintextBytes`. At startup, derive capacity with `resolveFeedbackBudget(validatedFeedbackConfig).maxTransportBytes` from `@h-ai/api-contract/feedback`, independently of optional Feedback initialization/retries; `*` matches one complete path segment and explicit host route values take precedence. Handshakes, adapter-node and proxies use the maximum route capacity. HTTP adapters admit at most 2 explicitly declared material requests above 1 MiB and a separate 16 ordinary business requests, releasing admission after the entire response or failure. Busy admission returns 503 / `hai:crypto:072`; business writes are not automatically replayed.
|
|
72
|
+
|
|
73
|
+
`keyScreenshots` defaults to false; automatic key screenshots require opt-in, while manual `prepare()` can still capture. Timeouts propagate AbortSignal and actual unfinished rendering retains the single-flight lock. The default renderer caps DOM traversal at 10000 nodes alongside its canvas pixel budget. With `replay:true`, `replayStart` defaults to `prepare`; pre-recording requires explicit `replayStart: init`. Call `pause()` when closing feedback to stop DOM replay/screenshots while retaining lightweight logs, drafts and uploads. Identity exit still calls `close({ discard:true })`. Custom `captureScreenshot({ signal })` should honor cancellation.
|
|
72
74
|
|
|
73
75
|
Automatic screenshots reconstruct the visible page from a redacted DOM clone through SVG foreignObject. Cross-origin images, iframe/video content and network fonts that are not ready may be missing. Browser or CSP failures are recorded as missing, and users can still attach images manually.
|
|
74
76
|
|
|
@@ -166,11 +168,19 @@ The viewer binds only to loopback, uses a random URL and trusted built-in viewer
|
|
|
166
168
|
|
|
167
169
|
Check `success` for every domain result. `HaiFeedbackError` covers initialization, configuration, scope, missing records, conflicts, quota and checksum failures. HTTP 200 and SMTP acceptance do not prove final business success or inbox delivery; uncertain sends can be repeated.
|
|
168
170
|
|
|
171
|
+
### Resource limits and optional availability
|
|
172
|
+
|
|
173
|
+
Server error-window pending evidence is bounded by 3000 records, 8 MiB and five minutes. Idempotent batches contain at most 32 records/256 KiB; database failures back off to 30 seconds and missing markers describe loss. Idle workers back off to 30 seconds; local submit/retry/export wakes them. `workerIntervalMs:0` requires a dedicated driver. Temporary evidence cleanup uses appId, an expiry index and at most 100 rows per batch.
|
|
174
|
+
|
|
175
|
+
Defaults: `maxReportsPerUserDay=20`, `maxBytesPerUserDay=2 GiB`, `maxStoredBytesPerScope=10 GiB`, `maxExportsPerReport=5`, `exportRetentionHours=24`. One shared DB transaction reserves `maxTotalBytes * (maxExportsPerReport + 1)` bytes per report for its materials and historical ZIPs, using the database UTC day and app/scope/user. Report capacity and export count are fixed at intake; raising configuration cannot exceed existing reservations. This is conservative reservation, not live disk accounting. Idempotent retries do not consume another quota; rejection returns `QUOTA_EXCEEDED` (429) without a report/object. Successful report retention cleanup releases scope reservation; daily totals are retained. Expired exports cannot be downloaded and objects are deleted in bounded batches. Frozen snapshots/timestamps and conditional writes reuse one ZIP; business collection freezes its first file manifest before upload. Monitor period=0 scope rows in `hai_feedback_quotas` and pending expiry cleanup jobs.
|
|
176
|
+
|
|
177
|
+
Reference admin/fullstack feedback startup failures leave required business modules ready, retry independently every 30 seconds and suppress unchanged warning spam; policy returns 503 until ready. Required DB/cache/IAM/audit retain their existing failure policy. Do not catch the entire failed runtime.start and continue after its rollback. Stop feedback and its retry before closing borrowed dependencies.
|
|
178
|
+
|
|
169
179
|
### Privacy boundary of console notifications
|
|
170
180
|
|
|
171
181
|
`consoleContent` defaults to `metadata`: report number, application/version/build identity and collection status only. Descriptions, summaries, attachments, evidence, notification bodies and destinations are excluded. Explicit `summary` enables a generically sanitized summary; generic redaction cannot identify every business secret. Reach is not required and disabled/reach channel semantics are unchanged. Existing console configurations become metadata-only by default on upgrade.
|
|
172
182
|
|
|
173
|
-
|
|
183
|
+
Server environment evidence commits in the intake transaction. A failed environment write leaves no accepted partial report; retry with the same submissionId. Management screens use `createFeedbackReview(...).retryReport(id)` for recoverable collection and failed notification/Webhook jobs. It deduplicates targets, propagates failures, and stops further orchestration after close.
|
|
174
184
|
|
|
175
185
|
## Development and testing
|
|
176
186
|
|
package/README.ja.md
CHANGED
|
@@ -54,7 +54,7 @@ return submitted
|
|
|
54
54
|
|
|
55
55
|
`text/plain` 添付は 5 MiB のファイル上限内で全文を秘匿処理し、安全な本文と末尾のログを保持します。構造化証拠のフィールド切り捨ては適用しません。上限を超える元ファイルは読取前に拒否し、秘匿処理後も同じ添付バイト上限を適用します。JSON 添付と構造化証拠には文字列長・深さ・件数の上限が残り、切り捨てられる場合があります。完全なログにはプレーンテキスト添付を使用してください。送信前の添付と、初回送信内容の固定後にローカル出力へ追加したファイルに同じ規則を適用します。
|
|
56
56
|
|
|
57
|
-
クライアントの全操作は `init(config)`、`prepare/captureScreenshot(attachmentId?)/submit/flush/record/getDraft/getView/annotate/addFiles/exportDraft/saveDraft/close` です。フォームの `onfileschange` を `addFiles` に接続し、下書きの添付プレビューを更新すると、追加画像を送信前に注釈・遮蔽・削除できます。重要操作は `data-feedback-action`、機密部分は `data-feedback-private`、フィードバック画面自体の除外は `data-feedback-ignore` で指定します。初回送信後の内容は固定され、再試行中の入力変更は競合として入力を保持し、ローカル保存できます。
|
|
57
|
+
クライアントの全操作は `init(config)`、`prepare/captureScreenshot(attachmentId?)/submit/flush/record/getDraft/getView/annotate/addFiles/exportDraft/saveDraft/pause/close` です。フォームの `onfileschange` を `addFiles` に接続し、下書きの添付プレビューを更新すると、追加画像を送信前に注釈・遮蔽・削除できます。重要操作は `data-feedback-action`、機密部分は `data-feedback-private`、フィードバック画面自体の除外は `data-feedback-ignore` で指定します。初回送信後の内容は固定され、再試行中の入力変更は競合として入力を保持し、ローカル保存できます。
|
|
58
58
|
|
|
59
59
|
`addFiles` は一括追加を原子的に行い、全ファイルの検証に成功してから統合します。一つでも失敗した場合、既存の添付と欠落マーカーを維持し、一部だけを保存しません。並行して成功した追加を上書きせず、統合後も最大 20 件です。読取中に下書きが変わるか初回送信内容が固定された場合は `CONFLICT` を返し、送信内容に含まれない添付を後から隠れてアップロードしません。
|
|
60
60
|
|
|
@@ -66,9 +66,11 @@ return submitted
|
|
|
66
66
|
|
|
67
67
|
下書きは SDK が保持し、`annotate` や `addFiles` で更新します。Svelte 側では参照を `$state.raw<FeedbackDraft>()` に保持し、操作成功後に `getDraft()` から添付ビューを更新してください。深い `$state` プロキシに格納すると、SDK が元の下書きの画像バイトを置き換えても、キャッシュされた古いバイトが返る場合があります。
|
|
68
68
|
|
|
69
|
-
`maxTotalBytes` の既定値は 32 MiB(4–40 MiB)です。`feedback.budget` から単一・合計添付、二重保存する証拠と Base64/暗号化転送の上限を導出します。既定は各 5 MiB、添付合計 22 MiB、証拠 4 MiB/2000 件、履歴・メタデータ・ビューアー用 2 MiB です。受理前に添付宣言の合計を検証し、証拠更新は原子的に制限、処理履歴は 256 KiB までです。認証済み `reports.policy({})`
|
|
69
|
+
`maxTotalBytes` の既定値は 32 MiB(4–40 MiB)です。`feedback.budget` から単一・合計添付、二重保存する証拠と Base64/暗号化転送の上限を導出します。既定は各 5 MiB、添付合計 22 MiB、証拠 4 MiB/2000 件、履歴・メタデータ・ビューアー用 2 MiB です。受理前に添付宣言の合計を検証し、証拠更新は原子的に制限、処理履歴は 256 KiB までです。認証済み `reports.policy({})` でクライアントとフォームに有効値を渡します。
|
|
70
70
|
|
|
71
|
-
`
|
|
71
|
+
通常業務は `transport.maxPlaintextBytes`(auto/省略は 1 MiB)を維持します。Feedback の受理・証拠・添付・ZIP ダウンロードは `routePlaintextBytes` で別途宣言します。起動時は `@h-ai/api-contract/feedback` の `resolveFeedbackBudget(検証済み設定).maxTransportBytes` を使い、任意の Feedback 初期化・再試行状態に依存しません。`*` は完全な1パス区間のみ一致し、ホストの明示値が優先です。ハンドシェイク、adapter-node、プロキシは経路の最大値を使用します。HTTP アダプターは個別宣言された 1 MiB 超の資料経路を最大2件、通常業務を別枠で16件まで許可し、応答完了・失敗時に解放します。混雑は503 / `hai:crypto:072` を返し、業務書込は自動再送しません。
|
|
72
|
+
|
|
73
|
+
`keyScreenshots` の既定値は false で、自動撮影は明示的に有効化します。手動 `prepare()` は撮影可能です。タイムアウトは AbortSignal を伝え、実処理の終了まで重複撮影を禁止します。既定レンダラーは10000 DOMノードとキャンバス画素予算を制限します。`replay:true` の `replayStart` は既定 `prepare` です。事前記録には `replayStart: init` を明示します。画面を閉じる際の `pause()` はDOM記録と撮影を止め、軽量ログ・下書き・アップロードを維持します。ユーザー切替時は引き続き `close({ discard:true })` を呼び、独自撮影関数は `{ signal }` を尊重します。
|
|
72
74
|
|
|
73
75
|
自動画像は機密部分を隠したクローン DOM から SVG foreignObject を通して可視ページを再構築します。クロスオリジン画像、iframe/video、未準備のネットワークフォントは欠落する場合があります。ブラウザーや CSP 制限による失敗は missing として記録され、画像の手動添付も利用できます。
|
|
74
76
|
|
|
@@ -166,11 +168,19 @@ hai feedback view feedback-FB-1.zip
|
|
|
166
168
|
|
|
167
169
|
各業務結果の `success` を確認してください。`HaiFeedbackError` は初期化、設定、権限、存在しない記録、競合、容量、checksum エラーを表します。HTTP 200やSMTP受理だけでは、業務成功や受信箱への配信を証明できません。不確実な送信は重複する可能性があります。
|
|
168
170
|
|
|
171
|
+
### リソース、クォータと補助機能の可用性
|
|
172
|
+
|
|
173
|
+
サーバーエラー証拠の待機列は3000件・8 MiB・5分を上限とし、最大32件/256 KiBの冪等バッチで保存します。DB障害は最大30秒までバックオフし、欠落理由を保存します。空のworkerも最大30秒まで待機し、同じノードでの送信・再試行・出力が起動します。`workerIntervalMs:0` には専用実行元が必要です。仮証拠はappId・期限索引を使って最大100件ずつ削除します。
|
|
174
|
+
|
|
175
|
+
既定値は `maxReportsPerUserDay=20`、`maxBytesPerUserDay=2 GiB`、`maxStoredBytesPerScope=10 GiB`、`maxExportsPerReport=5`、`exportRetentionHours=24` です。共有DBの同一トランザクションで、DBのUTC日とapp/scope/user別に `maxTotalBytes * (maxExportsPerReport + 1)` バイトを予約します。報告容量と出力数は受理時に固定し、設定を増やしても既存予約を超えません。これは保守的な予約量で実ディスク計測ではありません。冪等再送は再計上せず、超過は429 `QUOTA_EXCEEDED` とし報告・オブジェクトを作りません。報告の期限削除成功でscope予約を解放し、日次累計は戻しません。期限切れ出力はダウンロード不可で段階削除します。固定スナップショット・作成日時と条件付書込でZIPを再利用し、業務証拠のファイル一覧は最初のアップロード前に固定します。`hai_feedback_quotas` のperiod=0と期限cleanupの滞留を監視し。
|
|
176
|
+
|
|
177
|
+
参考admin/fullstackはfeedback起動失敗を業務の起動失敗にせず、30秒ごとに個別再試行し、同じ警告を繰り返しません。未準備のpolicyも503です。必須のDB/cache/IAM/auditは従来の障害方針を維持します。runtime.start全体の失敗を握りつぶしてロールバック済みのモジュールを使わないでください。終了時はfeedbackと再試行を停止してから依存を解放します。
|
|
178
|
+
|
|
169
179
|
### console 通知のプライバシー境界
|
|
170
180
|
|
|
171
181
|
`consoleContent` の既定は `metadata` で、報告番号、アプリ・バージョン・ビルド情報と収集状態だけを記録します。説明・概要・添付・証拠・通知本文・宛先は含みません。一般的なマスキング済み概要が必要な場合のみ `summary` を明示します。一般的なマスキングでは全業務機密を検出できません。Reach は不要で disabled/reach の意味は変わりません。更新後の既存 console 設定も既定でメタデータだけになります。
|
|
172
182
|
|
|
173
|
-
|
|
183
|
+
サーバー環境証拠は受付トランザクションで同時に保存します。環境保存失敗時は不完全な受付を残さず、同じ submissionId で再試行できます。管理画面は `createFeedbackReview(...).retryReport(id)` を使い、復旧可能な収集と通知/Webhook失敗を再試行します。対象を重複排除し失敗を伝播し、close後は後続処理を止めます。
|
|
174
184
|
|
|
175
185
|
## 開発とテスト
|
|
176
186
|
|
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ return submitted
|
|
|
56
56
|
|
|
57
57
|
`text/plain` 附件在 5 MiB 文件上限内全文脱敏,保留安全正文和末尾日志,不套用结构化证据的单字段截断。超过上限的原文件在读取前拒绝,脱敏结果仍受同一附件字节上限约束。JSON 附件和结构化证据继续限制字段长度、深度及数量,可能被裁剪;需要完整日志时使用纯文本附件。待提交附件与首次包络冻结后本地导出的新增附件使用相同规则。
|
|
58
58
|
|
|
59
|
-
完整客户端方法为 `init(config)`、`prepare/captureScreenshot(attachmentId?)/submit/flush/record/getDraft/getView/annotate/addFiles/exportDraft/saveDraft/close`。将表单 `onfileschange` 连接 `addFiles` 后刷新草稿附件预览,用户便可在提交前标注、遮挡或删除新增图片。`data-feedback-action` 标记关键操作,`data-feedback-private` 遮挡敏感内容,`data-feedback-ignore` 排除反馈界面自身。首次发送后包络冻结;同一提交重试时修改输入会返回冲突并保留输入,可先本地导出。
|
|
59
|
+
完整客户端方法为 `init(config)`、`prepare/captureScreenshot(attachmentId?)/submit/flush/record/getDraft/getView/annotate/addFiles/exportDraft/saveDraft/pause/close`。将表单 `onfileschange` 连接 `addFiles` 后刷新草稿附件预览,用户便可在提交前标注、遮挡或删除新增图片。`data-feedback-action` 标记关键操作,`data-feedback-private` 遮挡敏感内容,`data-feedback-ignore` 排除反馈界面自身。首次发送后包络冻结;同一提交重试时修改输入会返回冲突并保留输入,可先本地导出。
|
|
60
60
|
|
|
61
61
|
`addFiles` 按批次原子添加:全部文件通过校验才合并,任一失败均保留原附件和缺失标记,不保存半批文件。并发成功批次不会相互覆盖,合并后仍最多 20 项;读取期间草稿切换或首次包络冻结时返回 `CONFLICT`,不会补入包络之外的隐藏上传。
|
|
62
62
|
|
|
@@ -68,9 +68,11 @@ return submitted
|
|
|
68
68
|
|
|
69
69
|
草稿由 SDK 持有并通过 `annotate`、`addFiles` 等方法更新。Svelte 宿主保存该引用时使用 `$state.raw<FeedbackDraft>()`,操作成功后从 `getDraft()` 刷新附件视图;不要将 SDK 草稿放入深度 `$state` 代理,否则 SDK 替换图片字节后,代理可能继续返回已缓存的旧字节。
|
|
70
70
|
|
|
71
|
-
`maxTotalBytes` 默认 32 MiB(4–40 MiB)。`feedback.budget` 从同一总量推导单附件、附件总量、双份证据与 Base64/加密传输预算;默认单附件 5 MiB、附件合计 22 MiB、证据 4 MiB/2000 条,并预留 2 MiB 给历史、元数据和查看器。受理前检查附件声明总量,补采原子检查证据预算,处理历史限制 256 KiB,避免成功受理后因容量永久无法导出。客户端通过受权 `reports.policy({})`
|
|
71
|
+
`maxTotalBytes` 默认 32 MiB(4–40 MiB)。`feedback.budget` 从同一总量推导单附件、附件总量、双份证据与 Base64/加密传输预算;默认单附件 5 MiB、附件合计 22 MiB、证据 4 MiB/2000 条,并预留 2 MiB 给历史、元数据和查看器。受理前检查附件声明总量,补采原子检查证据预算,处理历史限制 256 KiB,避免成功受理后因容量永久无法导出。客户端通过受权 `reports.policy({})` 取得有效值,表单展示容量;
|
|
72
72
|
|
|
73
|
-
`
|
|
73
|
+
普通业务仍使用 `transport.maxPlaintextBytes`(auto/省略为 1 MiB);Feedback 的报告受理、证据、附件及 ZIP 下载路由通过 `routePlaintextBytes` 单独声明容量。启动时用 `@h-ai/api-contract/feedback` 的 `resolveFeedbackBudget(已校验的反馈配置).maxTransportBytes` 预计算,避免可选反馈故障/重试期间缓存模块默认预算。路径中的 `*` 仅匹配一个完整路径段;宿主显式路由值优先。握手、adapter-node 与代理使用各路由最大容量,普通业务不会随材料预算扩大。HTTP 适配器为单独声明且大于 1 MiB 的材料路由保留最多 2 个槽,普通业务另有 16 个槽,完整响应/失败后释放;忙碌返回 503 / `hai:crypto:072`,不自动重放业务写入。
|
|
74
|
+
|
|
75
|
+
`keyScreenshots` 默认 false,需显式开启自动关键截图;手动 `prepare()` 仍可截图。截图超时会传递 AbortSignal,底层尚未结束时不允许重叠截图。默认渲染最多 10000 DOM 节点,继续受画布像素预算限制。`replay:true` 的默认 `replayStart` 为 `prepare`,打开反馈时才录制;明确需要预录时配置 `replayStart: init`。关闭反馈界面调用 `pause()` 停止 DOM 回放和截图,保留轻量日志、草稿及上传;退出身份仍调用 `close({ discard:true })`。自定义 `captureScreenshot({ signal })` 应尊重取消信号。
|
|
74
76
|
|
|
75
77
|
自动截图由脱敏克隆 DOM 经 SVG foreignObject 重建可见页面。跨域图片、iframe、video 和尚未就绪的网络字体可能缺失;浏览器或 CSP 限制导致失败时明确标记 missing,用户仍可手动添加附件。
|
|
76
78
|
|
|
@@ -168,11 +170,19 @@ hai feedback view feedback-FB-1.zip
|
|
|
168
170
|
|
|
169
171
|
领域错误使用 `HaiFeedbackError`,包括未初始化、配置、权限、找不到、冲突、容量及 checksum 失败。所有领域 API 先检查 `success`;HTTP 200 或 Promise 完成不代表反馈、上传或邮件成功。SMTP 受理不等于管理员收件箱已投递;不确定发送存在重发边界。
|
|
170
172
|
|
|
173
|
+
### 资源、配额与辅助服务故障
|
|
174
|
+
|
|
175
|
+
服务端错误窗口待写材料同时限制 3000 条、8 MiB 和 5 分钟;按最多 32 条/256 KiB 批量幂等写入,数据库失败指数退避到 30 秒,丢失保留 missing 原因。worker 空闲指数退避到 30 秒,本节点提交/重试/导出会唤醒;`workerIntervalMs:0` 由专用 worker 驱动。临时证据按 appId 每批最多 100 条清理,使用复合过期索引。
|
|
176
|
+
|
|
177
|
+
`maxReportsPerUserDay` 默认 20,`maxBytesPerUserDay` 默认 2 GiB,`maxStoredBytesPerScope` 默认 10 GiB,`maxExportsPerReport` 默认 5,`exportRetentionHours` 默认 24。受理在同一共享数据库事务中按数据库 UTC 日、app/scope/user 预留 `maxTotalBytes * (maxExportsPerReport + 1)` 字节,覆盖报告及历史 ZIP;受理时固定报告容量与导出数量,调高配置不突破既有预留;是保守容量预留,非磁盘实时计量。重复 submissionId 不重复计费,超限返回 `QUOTA_EXCEEDED`(429)且不创建报告或对象。成功到期删除报告才释放 scope 预留;日累计计数不退还。导出到期禁止下载并限批删对象,固定快照/创建时间与条件写复用同一个 ZIP,业务补采首次上传前冻结文件清单,重试不能变化 ID/内容制造无限对象。运维可查看 `hai_feedback_quotas` 的 scope 行(period=0)已预留量及到期 cleanup 任务积压;它不等于云存储账单。
|
|
178
|
+
|
|
179
|
+
参考管理台与 fullstack 的 feedback 启动失败仅使反馈不可用,30 秒独立重试且同类故障不重复输出告警;策略接口也返回 503。业务必需的数据库、缓存、IAM 和审计仍遵守原失败策略;不能捕获整个 runtime.start 后继续使用已经回滚的模块。关闭先停止反馈与重试任务,再释放借用依赖。
|
|
180
|
+
|
|
171
181
|
### console 通知的隐私边界
|
|
172
182
|
|
|
173
183
|
`consoleContent` 默认为 `metadata`:仅记录反馈编号、应用/版本/构建定位与采集状态,不输出说明、summary、附件、证据、通知正文或接收地址。需要通用脱敏摘要时显式设为 `summary`;通用脱敏不能识别所有业务秘密。该选项不需要 Reach,也不改变 disabled/reach 的通道语义。旧配置的 console 日志默认随本次升级收紧。
|
|
174
184
|
|
|
175
|
-
|
|
185
|
+
服务端环境证据与反馈受理共同提交;环境写入失败不留下已受理但缺失环境的半成品,相同 submissionId 可安全重试。管理界面用 `createFeedbackReview(...).retryReport(id)` 重试可恢复材料与失败通知/Webhook,控制器去重目标并传播失败,退出调用 close 阻止迟到请求继续编排。
|
|
176
186
|
|
|
177
187
|
## 开发测试
|
|
178
188
|
|
package/dist/bundle/index.d.ts
CHANGED
|
@@ -49,6 +49,8 @@ interface FeedbackBundleInput<TReport extends {
|
|
|
49
49
|
}> {
|
|
50
50
|
/** 有效总预算;不得高于通用离线格式的 64 MiB 防护上限。 */
|
|
51
51
|
maxBytes?: number;
|
|
52
|
+
/** 固定归档创建时间(Unix 毫秒),服务端幂等导出使用任务创建时间;默认当前时间。 */
|
|
53
|
+
createdAt?: number;
|
|
52
54
|
report: TReport;
|
|
53
55
|
evidence: readonly FeedbackBundleEvidence[];
|
|
54
56
|
history?: readonly object[];
|
package/dist/bundle/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { createFeedbackBundle, getFeedbackBundleViewerFiles, queryFeedbackBundle, readFeedbackBundle, summarizeFeedbackBundle } from '../chunk-
|
|
1
|
+
export { createFeedbackBundle, getFeedbackBundleViewerFiles, queryFeedbackBundle, readFeedbackBundle, summarizeFeedbackBundle } from '../chunk-KGMLTOMM.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
|
@@ -11,7 +11,8 @@ var FeedbackErrorInfo = {
|
|
|
11
11
|
CONFLICT: "023:409",
|
|
12
12
|
LIMIT_EXCEEDED: "024:413",
|
|
13
13
|
INVALID_INPUT: "025:400",
|
|
14
|
-
CHECKSUM_MISMATCH: "026:422"
|
|
14
|
+
CHECKSUM_MISMATCH: "026:422",
|
|
15
|
+
QUOTA_EXCEEDED: "027:429"
|
|
15
16
|
};
|
|
16
17
|
var HaiFeedbackError = core.error.buildHaiErrorsDef("feedback", FeedbackErrorInfo);
|
|
17
18
|
|
|
@@ -37,7 +38,8 @@ var en_US_default = {
|
|
|
37
38
|
feedback_materialPartial: "Partially missing",
|
|
38
39
|
feedback_materialFailed: "Collection failed",
|
|
39
40
|
feedback_defaultSummary: "User feedback",
|
|
40
|
-
feedback_retryInputChanged: "The first submission has already been sent. Restore the original text and attachments to retry, then open a new feedback."
|
|
41
|
+
feedback_retryInputChanged: "The first submission has already been sent. Restore the original text and attachments to retry, then open a new feedback.",
|
|
42
|
+
feedback_quotaExceeded: "Feedback quota reached. Try later or contact an administrator."
|
|
41
43
|
};
|
|
42
44
|
|
|
43
45
|
// messages/ja-JP.json
|
|
@@ -62,7 +64,8 @@ var ja_JP_default = {
|
|
|
62
64
|
feedback_materialPartial: "\u4E00\u90E8\u4E0D\u8DB3",
|
|
63
65
|
feedback_materialFailed: "\u53CE\u96C6\u5931\u6557",
|
|
64
66
|
feedback_defaultSummary: "\u30E6\u30FC\u30B6\u30FC\u306E\u30D5\u30A3\u30FC\u30C9\u30D0\u30C3\u30AF",
|
|
65
|
-
feedback_retryInputChanged: "\u6700\u521D\u306E\u9001\u4FE1\u306F\u958B\u59CB\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u5143\u306E\u8AAC\u660E\u3068\u6DFB\u4ED8\u30D5\u30A1\u30A4\u30EB\u306B\u623B\u3057\u3066\u9001\u4FE1\u7D50\u679C\u3092\u78BA\u8A8D\u3057\u3066\u304B\u3089\u3001\u65B0\u3057\u3044\u30D5\u30A3\u30FC\u30C9\u30D0\u30C3\u30AF\u3092\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
67
|
+
feedback_retryInputChanged: "\u6700\u521D\u306E\u9001\u4FE1\u306F\u958B\u59CB\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u5143\u306E\u8AAC\u660E\u3068\u6DFB\u4ED8\u30D5\u30A1\u30A4\u30EB\u306B\u623B\u3057\u3066\u9001\u4FE1\u7D50\u679C\u3092\u78BA\u8A8D\u3057\u3066\u304B\u3089\u3001\u65B0\u3057\u3044\u30D5\u30A3\u30FC\u30C9\u30D0\u30C3\u30AF\u3092\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
68
|
+
feedback_quotaExceeded: "\u30D5\u30A3\u30FC\u30C9\u30D0\u30C3\u30AF\u306E\u4E0A\u9650\u306B\u9054\u3057\u307E\u3057\u305F\u3002\u5F8C\u3067\u518D\u8A66\u884C\u3059\u308B\u304B\u7BA1\u7406\u8005\u306B\u9023\u7D61\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
66
69
|
};
|
|
67
70
|
|
|
68
71
|
// messages/zh-CN.json
|
|
@@ -87,7 +90,8 @@ var zh_CN_default = {
|
|
|
87
90
|
feedback_materialPartial: "\u90E8\u5206\u7F3A\u5931",
|
|
88
91
|
feedback_materialFailed: "\u91C7\u96C6\u5931\u8D25",
|
|
89
92
|
feedback_defaultSummary: "\u7528\u6237\u63D0\u4EA4\u7684\u95EE\u9898\u53CD\u9988",
|
|
90
|
-
feedback_retryInputChanged: "\u9996\u6B21\u63D0\u4EA4\u5DF2\u53D1\u51FA\uFF0C\u91CD\u8BD5\u9700\u4FDD\u6301\u539F\u8BF4\u660E\u548C\u9644\u4EF6\u3002\u8BF7\u6062\u590D\u539F\u8F93\u5165\u5B8C\u6210\u786E\u8BA4\u540E\uFF0C\u518D\u53D1\u8D77\u65B0\u53CD\u9988\u3002"
|
|
93
|
+
feedback_retryInputChanged: "\u9996\u6B21\u63D0\u4EA4\u5DF2\u53D1\u51FA\uFF0C\u91CD\u8BD5\u9700\u4FDD\u6301\u539F\u8BF4\u660E\u548C\u9644\u4EF6\u3002\u8BF7\u6062\u590D\u539F\u8F93\u5165\u5B8C\u6210\u786E\u8BA4\u540E\uFF0C\u518D\u53D1\u8D77\u65B0\u53CD\u9988\u3002",
|
|
94
|
+
feedback_quotaExceeded: "\u53CD\u9988\u914D\u989D\u5DF2\u7528\u5B8C\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u6216\u8054\u7CFB\u7BA1\u7406\u5458"
|
|
91
95
|
};
|
|
92
96
|
|
|
93
97
|
// src/feedback-i18n.ts
|
|
@@ -97,5 +101,5 @@ function redactFeedbackText(text) {
|
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
export { HaiFeedbackError, feedbackM, redactFeedbackText };
|
|
100
|
-
//# sourceMappingURL=chunk-
|
|
101
|
-
//# sourceMappingURL=chunk-
|
|
104
|
+
//# sourceMappingURL=chunk-7W47WZXY.js.map
|
|
105
|
+
//# sourceMappingURL=chunk-7W47WZXY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/feedback-types.ts","../messages/en-US.json","../messages/ja-JP.json","../messages/zh-CN.json","../src/feedback-i18n.ts","../src/feedback-sanitize-text.ts"],"names":["core"],"mappings":"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","file":"chunk-7W47WZXY.js","sourcesContent":["import type {\n FeedbackAppendEvidenceInput,\n FeedbackAttachment,\n FeedbackAttachmentData,\n FeedbackBudget,\n FeedbackCompleteInput,\n FeedbackDetail,\n FeedbackEvidence,\n FeedbackEvidenceInput,\n FeedbackEvidenceQuery,\n FeedbackExportInput,\n FeedbackJob,\n FeedbackListInput,\n FeedbackReport,\n FeedbackRetryInput,\n FeedbackSetStatusInput,\n FeedbackSubmitInput,\n FeedbackUploadInput,\n} from '@h-ai/api-contract/feedback'\nimport type { ErrorInfo, HaiResult, PaginatedResult } from '@h-ai/core'\nimport type { FeedbackConfig, FeedbackConfigInput } from './feedback-config.js'\nimport { core } from '@h-ai/core'\n\nconst FeedbackErrorInfo = {\n NOT_INITIALIZED: '010:503',\n INIT_IN_PROGRESS: '011:409',\n CONFIG_ERROR: '013:500',\n OPERATION_FAILED: '020:500',\n NOT_FOUND: '021:404',\n FORBIDDEN: '022:403',\n CONFLICT: '023:409',\n LIMIT_EXCEEDED: '024:413',\n INVALID_INPUT: '025:400',\n CHECKSUM_MISMATCH: '026:422',\n QUOTA_EXCEEDED: '027:429',\n} satisfies ErrorInfo\n\n/**\n * Feedback 错误定义。\n * @example\n * ```ts\n * import { HaiFeedbackError } from '@h-ai/feedback'\n * const code = HaiFeedbackError.NOT_FOUND.code\n * void code\n * ```\n */\nexport const HaiFeedbackError = core.error.buildHaiErrorsDef('feedback', FeedbackErrorInfo)\n\n/**\n * 由可信服务端认证与授权策略生成的范围;HTTP 输入不能指定此对象。\n * @example\n * ```ts\n * import type { FeedbackContext } from '@h-ai/feedback'\n * const context: FeedbackContext = { scopeId: 'tenant-1', userId: 'user-1', permissions: ['feedback:create'] }\n * void context\n * ```\n */\nexport interface FeedbackContext {\n scopeId: string\n userId: string\n permissions?: readonly string[]\n}\n\n/**\n * 业务采集附件;ID 在同一反馈内稳定,正文由框架计算校验和并写入私有存储。\n * @example\n * ```ts\n * import type { FeedbackCollectedAttachment } from '@h-ai/feedback'\n * const file: FeedbackCollectedAttachment = { id: 'logs', name: 'logs.txt', contentType: 'text/plain', bytes: new TextEncoder().encode('diagnostics') }\n * void file\n * ```\n */\nexport interface FeedbackCollectedAttachment {\n id: string\n name: string\n contentType: string\n bytes: Uint8Array\n}\n\n/**\n * 业务补充材料;短文本/结构化信息出现在 business 证据,完整日志使用附件。\n * @example\n * ```ts\n * import type { FeedbackCollectedEvidence } from '@h-ai/feedback'\n * const evidence: FeedbackCollectedEvidence = { text: 'Order diagnostics', data: { orderId: 'order-1' } }\n * void evidence\n * ```\n */\nexport interface FeedbackCollectedEvidence {\n text?: string\n data?: FeedbackEvidenceInput['data']\n attachments?: FeedbackCollectedAttachment[]\n}\n\n/**\n * 采集扩展在持久任务中执行并等待异步结果;可能重试,宿主应按 report.id 幂等并响应 signal。\n * @example\n * ```ts\n * import type { FeedbackInitOptions } from '@h-ai/feedback'\n * const options: FeedbackInitOptions = { collectEvidence: async ({ report, signal }) => { signal.throwIfAborted(); return { text: report.number } } }\n * void options\n * ```\n */\nexport interface FeedbackInitOptions {\n collectEvidence?: (context: { report: Readonly<FeedbackReport>, signal: AbortSignal }) => FeedbackCollectedEvidence | Promise<FeedbackCollectedEvidence>\n}\n\n/**\n * 采集结束后发送的 feedback.created 事件;eventId 跨重试稳定,附件正文通过受权 API 读取。\n * @example\n * ```ts\n * import type { FeedbackWebhookEvent } from '@h-ai/feedback'\n * function eventKey(event: FeedbackWebhookEvent) { return event.eventId }\n * void eventKey\n * ```\n */\nexport interface FeedbackWebhookEvent {\n event: 'feedback.created'\n eventId: string\n createdAt: number\n report: FeedbackReport\n evidence: { revision: number, business: boolean, attachments: FeedbackAttachment[] }\n detailUrl?: string\n}\n\n/**\n * 反馈服务对象;领域操作返回 HaiResult,close 不释放宿主依赖连接。\n * @example\n * ```ts\n * import type { FeedbackContext, FeedbackFunctions } from '@h-ai/feedback'\n * async function submit(service: FeedbackFunctions, context: FeedbackContext) {\n * const result = await service.submit({ appId: 'orders', submissionId: 'stable-operation-id' }, context)\n * return result.success ? result.data.id : result.error.code\n * }\n * void submit\n * ```\n */\nexport interface FeedbackFunctions {\n init: (config: FeedbackConfigInput, options?: FeedbackInitOptions) => Promise<HaiResult<void>>\n close: () => Promise<HaiResult<void>>\n readonly isInitialized: boolean\n readonly config: FeedbackConfig | null\n readonly budget: FeedbackBudget\n submit: (input: FeedbackSubmitInput, context: FeedbackContext) => Promise<HaiResult<FeedbackReport>>\n list: (input: FeedbackListInput, context: FeedbackContext) => Promise<HaiResult<PaginatedResult<FeedbackReport>>>\n get: (id: string, context: FeedbackContext) => Promise<HaiResult<FeedbackDetail>>\n getEvidence: (input: FeedbackEvidenceQuery, context: FeedbackContext) => Promise<HaiResult<PaginatedResult<FeedbackEvidence>>>\n setStatus: (input: FeedbackSetStatusInput, context: FeedbackContext) => Promise<HaiResult<FeedbackReport>>\n appendEvidence: (input: FeedbackAppendEvidenceInput, context: FeedbackContext) => Promise<HaiResult<{ acknowledged: string[], evidenceRevision: number }>>\n completeCollection: (input: FeedbackCompleteInput, context: FeedbackContext) => Promise<HaiResult<FeedbackReport>>\n uploadAttachment: (input: FeedbackUploadInput, context: FeedbackContext) => Promise<HaiResult<FeedbackAttachment>>\n getAttachment: (input: { id: string, attachmentId: string }, context: FeedbackContext) => Promise<HaiResult<FeedbackAttachmentData>>\n retry: (input: FeedbackRetryInput, context: FeedbackContext) => Promise<HaiResult<void>>\n export: (input: FeedbackExportInput, context: FeedbackContext) => Promise<HaiResult<FeedbackJob>>\n getExport: (input: { id: string, jobId: string }, context: FeedbackContext) => Promise<HaiResult<FeedbackAttachmentData>>\n runJobs: () => Promise<HaiResult<{ processed: number }>>\n}\n\nexport type {\n FeedbackAppendEvidenceInput,\n FeedbackAttachment,\n FeedbackAttachmentData,\n FeedbackCompleteInput,\n FeedbackDetail,\n FeedbackEvidence,\n FeedbackEvidenceQuery,\n FeedbackExportInput,\n FeedbackJob,\n FeedbackListInput,\n FeedbackReport,\n FeedbackRetryInput,\n FeedbackSetStatusInput,\n FeedbackSubmitInput,\n FeedbackUploadInput,\n} from '@h-ai/api-contract/feedback'\n","{\n \"feedback_notInitialized\": \"Feedback has not been initialized\",\n \"feedback_initInProgress\": \"Feedback initialization is in progress\",\n \"feedback_configError\": \"Invalid feedback configuration or unavailable dependency\",\n \"feedback_operationFailed\": \"Feedback operation failed. Please retry\",\n \"feedback_notFound\": \"Feedback or evidence was not found\",\n \"feedback_forbidden\": \"Access to this feedback is denied\",\n \"feedback_conflict\": \"Feedback changed or an operation identifier conflicts. Refresh and retry\",\n \"feedback_exportPending\": \"The feedback bundle is being generated. Please retry shortly\",\n \"feedback_limitExceeded\": \"Feedback evidence exceeds the allowed capacity\",\n \"feedback_invalidInput\": \"Invalid feedback input\",\n \"feedback_unsupportedAttachmentType\": \"Unsupported attachment format. Choose a PNG, JPEG, WebP, WebM, MP4, TXT, LOG, JSON, PDF, ZIP or GZIP file\",\n \"feedback_checksumMismatch\": \"Attachment integrity verification failed\",\n \"feedback_received\": \"New feedback received\",\n \"feedback_submittedAt\": \"Submitted at (UTC)\",\n \"feedback_materialStatus\": \"Material status\",\n \"feedback_materialCollecting\": \"Collecting\",\n \"feedback_materialComplete\": \"Complete\",\n \"feedback_materialPartial\": \"Partially missing\",\n \"feedback_materialFailed\": \"Collection failed\",\n \"feedback_defaultSummary\": \"User feedback\",\n \"feedback_retryInputChanged\": \"The first submission has already been sent. Restore the original text and attachments to retry, then open a new feedback.\",\n \"feedback_quotaExceeded\": \"Feedback quota reached. Try later or contact an administrator.\"\n}\n","{\n \"feedback_notInitialized\": \"フィードバックが初期化されていません\",\n \"feedback_initInProgress\": \"フィードバックを初期化しています\",\n \"feedback_configError\": \"設定が無効、または依存サービスが利用できません\",\n \"feedback_operationFailed\": \"操作に失敗しました。再試行してください\",\n \"feedback_notFound\": \"フィードバックまたは資料が見つかりません\",\n \"feedback_forbidden\": \"このフィードバックへのアクセス権限がありません\",\n \"feedback_conflict\": \"フィードバックが更新されたか操作識別子が重複しています。再読込してください\",\n \"feedback_exportPending\": \"問題パッケージを生成しています。しばらくしてから再試行してください\",\n \"feedback_limitExceeded\": \"資料が許容容量を超えています\",\n \"feedback_invalidInput\": \"入力が無効です\",\n \"feedback_unsupportedAttachmentType\": \"未対応の添付形式です。PNG、JPEG、WebP、WebM、MP4、TXT、LOG、JSON、PDF、ZIP、GZIP を選択してください\",\n \"feedback_checksumMismatch\": \"添付ファイルの整合性を確認できません\",\n \"feedback_received\": \"新しいフィードバックを受信しました\",\n \"feedback_submittedAt\": \"報告日時(UTC)\",\n \"feedback_materialStatus\": \"資料の状態\",\n \"feedback_materialCollecting\": \"収集中\",\n \"feedback_materialComplete\": \"完全\",\n \"feedback_materialPartial\": \"一部不足\",\n \"feedback_materialFailed\": \"収集失敗\",\n \"feedback_defaultSummary\": \"ユーザーのフィードバック\",\n \"feedback_retryInputChanged\": \"最初の送信は開始されています。元の説明と添付ファイルに戻して送信結果を確認してから、新しいフィードバックを作成してください。\",\n \"feedback_quotaExceeded\": \"フィードバックの上限に達しました。後で再試行するか管理者に連絡してください。\"\n}\n","{\n \"feedback_notInitialized\": \"反馈模块尚未初始化\",\n \"feedback_initInProgress\": \"反馈模块正在初始化\",\n \"feedback_configError\": \"反馈配置无效或前置服务不可用\",\n \"feedback_operationFailed\": \"反馈操作失败,请重试\",\n \"feedback_notFound\": \"反馈或材料不存在\",\n \"feedback_forbidden\": \"没有访问该反馈的权限\",\n \"feedback_conflict\": \"反馈已更新或操作标识冲突,请刷新后重试\",\n \"feedback_exportPending\": \"问题包正在生成,请稍后重试\",\n \"feedback_limitExceeded\": \"反馈材料超过允许的容量\",\n \"feedback_invalidInput\": \"反馈参数无效\",\n \"feedback_unsupportedAttachmentType\": \"不支持此附件格式,请选择 PNG、JPEG、WebP、WebM、MP4、TXT、LOG、JSON、PDF、ZIP 或 GZIP 文件\",\n \"feedback_checksumMismatch\": \"附件完整性校验失败\",\n \"feedback_received\": \"收到新的问题反馈\",\n \"feedback_submittedAt\": \"反馈时间(UTC)\",\n \"feedback_materialStatus\": \"材料状态\",\n \"feedback_materialCollecting\": \"收集中\",\n \"feedback_materialComplete\": \"完整\",\n \"feedback_materialPartial\": \"部分缺失\",\n \"feedback_materialFailed\": \"采集失败\",\n \"feedback_defaultSummary\": \"用户提交的问题反馈\",\n \"feedback_retryInputChanged\": \"首次提交已发出,重试需保持原说明和附件。请恢复原输入完成确认后,再发起新反馈。\",\n \"feedback_quotaExceeded\": \"反馈配额已用完,请稍后重试或联系管理员\"\n}\n","import { core } from '@h-ai/core'\nimport en from '../messages/en-US.json'\nimport ja from '../messages/ja-JP.json'\nimport zh from '../messages/zh-CN.json'\n\n/**\n * 模块内消息获取器。\n * @example 模块内部上下文\n * ```ts\n * const message = feedbackM('feedback_notFound')\n * void message\n * ```\n */\nexport const feedbackM = core.i18n.createMessageGetter<keyof typeof zh>({ 'zh-CN': zh, 'en-US': en, 'ja-JP': ja })\n","import { core } from '@h-ai/core'\n\n/**\n * Node 与 Browser 共用的自由文本凭据脱敏;URL 与结构化字段仍复用 core。\n * @example 模块内部上下文\n * ```ts\n * const safeText = redactFeedbackText('{\"password\":\"example-only\"}')\n * void safeText\n * ```\n */\nexport function redactFeedbackText(text: string): string {\n return core.sanitize.sanitizeText(text)\n}\n"]}
|
|
@@ -204,6 +204,8 @@ function parseManifest(value) {
|
|
|
204
204
|
}
|
|
205
205
|
async function createFeedbackBundle(input) {
|
|
206
206
|
try {
|
|
207
|
+
if (input.createdAt !== void 0 && (!Number.isSafeInteger(input.createdAt) || input.createdAt < 0))
|
|
208
|
+
throw new Error("Invalid bundle creation time");
|
|
207
209
|
const report = jsonObject(input.report);
|
|
208
210
|
if (typeof report.id !== "string" || !SAFE_ID.test(report.id))
|
|
209
211
|
throw new Error("Invalid report");
|
|
@@ -237,7 +239,7 @@ async function createFeedbackBundle(input) {
|
|
|
237
239
|
files[path] = new Uint8Array(attachment.bytes);
|
|
238
240
|
manifestFiles.push({ path, size: attachment.bytes.byteLength, sha256: hash, kind: "attachment", attachmentId: attachment.id, name: attachment.name, contentType: attachment.contentType });
|
|
239
241
|
}
|
|
240
|
-
const manifest = { format: "hai-feedback", version: 1, reportId: report.id, evidenceRevision: typeof report.evidenceRevision === "number" ? report.evidenceRevision : 0, createdAt: Date.now(), missing, files: manifestFiles };
|
|
242
|
+
const manifest = { format: "hai-feedback", version: 1, reportId: report.id, evidenceRevision: typeof report.evidenceRevision === "number" ? report.evidenceRevision : 0, createdAt: input.createdAt ?? Date.now(), missing, files: manifestFiles };
|
|
241
243
|
const temporary = { report: { ...report, id: report.id }, evidence, history, manifest, files };
|
|
242
244
|
files["summary.md"] = strToU8(summarizeFeedbackBundle(temporary));
|
|
243
245
|
for (const [path, bytes] of Object.entries(files)) {
|
|
@@ -334,5 +336,5 @@ function getFeedbackBundleViewerFiles() {
|
|
|
334
336
|
}
|
|
335
337
|
|
|
336
338
|
export { createFeedbackBundle, getFeedbackBundleViewerFiles, queryFeedbackBundle, readFeedbackBundle, summarizeFeedbackBundle };
|
|
337
|
-
//# sourceMappingURL=chunk-
|
|
338
|
-
//# sourceMappingURL=chunk-
|
|
339
|
+
//# sourceMappingURL=chunk-KGMLTOMM.js.map
|
|
340
|
+
//# sourceMappingURL=chunk-KGMLTOMM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bundle/feedback-bundle-viewer.ts","../src/bundle/feedback-bundle-functions.ts"],"names":["offset","strToU8"],"mappings":"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","file":"chunk-KGMLTOMM.js","sourcesContent":["import { strToU8 } from 'fflate'\n\ndeclare const __FEEDBACK_REPLAY_SCRIPT__: string\n\n/** 查看器只使用 textContent 与受限图片,不执行原应用或用户附件脚本。 */\nconst VIEWER_SCRIPT = `\nconst messages = {\n zh: {title:'问题反馈',facts:'采集事实',user:'用户描述',evidence:'证据',attachments:'截图与附件',missing:'缺失材料',history:'处理记录',error:'问题包读取失败',all:'全部',next:'下一页',previous:'上一页',download:'下载',empty:'未填写',replay:'页面回放',play:'播放',pause:'暂停',unavailable:'回放不可用,已保留原始证据',boundary:'回放展示采集的页面快照与交互,不执行原应用脚本。未内嵌的外部图片、字体和样式被离线策略阻止;缺失记录见下方。'},\n en: {title:'Feedback report',facts:'Collected facts',user:'User description',evidence:'Evidence',attachments:'Screenshots and attachments',missing:'Missing evidence',history:'Processing history',error:'Unable to read the bundle',all:'All',next:'Next',previous:'Previous',download:'Download',empty:'Not provided',replay:'Page replay',play:'Play',pause:'Pause',unavailable:'Replay unavailable; original evidence is preserved',boundary:'Replay shows captured snapshots and interactions without running application scripts. External images, fonts and styles that were not embedded are blocked by offline policy. See missing evidence below.'},\n ja: {title:'問題フィードバック',facts:'収集した事実',user:'ユーザーの説明',evidence:'証拠',attachments:'スクリーンショットと添付',missing:'不足資料',history:'処理記録',error:'パッケージを読み込めません',all:'すべて',next:'次へ',previous:'前へ',download:'ダウンロード',empty:'未入力',replay:'ページ再生',play:'再生',pause:'一時停止',unavailable:'再生できません。元の証拠は保持されています',boundary:'収集したスナップショットと操作を再生し、元のアプリのスクリプトは実行しません。埋め込まれていない外部画像、フォント、スタイルはオフライン方針で遮断されます。不足資料も確認してください。'}\n};\nconst language = navigator.language.startsWith('ja') ? 'ja' : navigator.language.startsWith('zh') ? 'zh' : 'en';\nconst m = messages[language];\nfunction text(tag,value,parent){const node=document.createElement(tag);node.textContent=String(value??'');parent.append(node);return node;}\nfunction section(title){const block=document.createElement('section');text('h2',title,block);document.querySelector('main').append(block);return block;}\nfunction safeImage(file){return ['image/png','image/jpeg','image/webp'].includes(file.contentType);}\nasync function load(path){const response=await fetch('../'+path);if(!response.ok)throw Error('Missing file');return response.json();}\ntry {\n const [manifest,report,evidence,history]=await Promise.all([load('manifest.json'),load('report.json'),load('evidence/records.json'),load('evidence/history.json')]);\n text('h1',m.title+' '+(report.number||report.id),document.querySelector('header'));\n text('pre',JSON.stringify(report,null,2),section(m.facts));\n text('p',report.description||m.empty,section(m.user));\n const attachments=section(m.attachments);\n for(const file of manifest.files.filter(item=>item.kind==='attachment')){\n if(!/^attachments\\\\/[\\\\w-]+\\\\.(png|jpg|webp|webm|mp4|txt|json|bin)$/.test(file.path))continue;\n const figure=document.createElement('figure');attachments.append(figure);text('figcaption',file.name||file.attachmentId,figure);\n if(safeImage(file)){const image=document.createElement('img');image.src='../'+file.path;image.alt=file.name||'';image.loading='lazy';figure.append(image);}\n if(['video/webm','video/mp4'].includes(file.contentType)){const video=document.createElement('video');video.src='../'+file.path;video.controls=true;video.preload='metadata';video.style.maxWidth='100%';figure.append(video);}\n const link=text('a',m.download,figure);link.href='../'+file.path;link.download=file.name||file.path.split('/').pop();\n }\n const replayEvents=evidence.filter(item=>item.kind==='replay'&&item.data&&item.data.event).map(item=>item.data.event).sort((a,b)=>a.timestamp-b.timestamp);\n\n if(replayEvents.length){\n const replay=section(m.replay);text('p',m.boundary,replay);\n try{\n await import('./replay.js');\n if(typeof globalThis.__HAI_FEEDBACK_REPLAY__!=='function')throw Error('Replay unavailable');\n const fullSnapshot=replayEvents.find(event=>event.type===2);\n if(!fullSnapshot)throw Error('Replay full snapshot unavailable');\n const target=document.createElement('div');replay.append(target);\n let player;\n const origin=replayEvents[0].timestamp;\n const duration=Math.max(0,replayEvents.at(-1).timestamp-origin);\n const snapshotOffset=Math.max(0,fullSnapshot.timestamp-origin);\n const play=text('button',m.play,replay);const pause=text('button',m.pause,replay);\n const slider=document.createElement('input');slider.type='range';slider.min='0';slider.max=String(Math.max(1,duration));slider.value='0';slider.setAttribute('aria-label',m.replay);replay.append(slider);\n let current=0;let playing=false;let frame=0;\n function position(value){return Math.max(0,Math.min(duration,Number.isFinite(value)?value:0));}\n function stop(){if(frame)cancelAnimationFrame(frame);frame=0;}\n // 每次定位重建实例,隔离旧回放末尾延迟到达的 finish,避免覆盖新的进度。\n function construct(){const instance=globalThis.__HAI_FEEDBACK_REPLAY__(replayEvents,target);player=instance;instance.on('finish',()=>{if(instance!==player||!playing)return;playing=false;stop();current=duration;slider.value=String(current);});}\n function update(){if(!playing)return;current=position(player.getCurrentTime());slider.value=String(current);frame=requestAnimationFrame(update);}\n // rrweb 仅同步应用严格早于偏移的事件;增加 1ms 使精确事件时间与首帧边界都包含在内。\n function seek(value){playing=false;stop();const requested=position(value);player?.destroy();construct();player.pause(Math.max(requested,snapshotOffset)+1);current=requested;slider.value=String(current);}\n\n play.onclick=()=>{stop();current=position(Number(slider.value));if(current>=duration)seek(0);playing=true;player.play(Math.max(current,snapshotOffset));update();};\n pause.onclick=()=>{player.pause();playing=false;stop();current=position(player.getCurrentTime());slider.value=String(current);};\n slider.oninput=()=>seek(Number(slider.value));\n window.addEventListener('pagehide',()=>{playing=false;stop();player.destroy();},{once:true});\n seek(0);\n }catch{text('p',m.unavailable,replay);}\n }\n const block=section(m.evidence);const filter=document.createElement('select');block.append(filter);const all=text('option',m.all,filter);all.value='';\n for(const kind of [...new Set(evidence.map(item=>item.kind))]){const option=text('option',kind,filter);option.value=kind;}\n const request=document.createElement('input');request.placeholder='requestId';request.setAttribute('aria-label','requestId');block.append(request);\n const output=text('pre','',block);const previous=text('button',m.previous,block);const next=text('button',m.next,block);const counter=text('span','',block);let page=1;\n function render(){const rows=evidence.filter(item=>(!filter.value||item.kind===filter.value)&&(!request.value||item.requestId===request.value));const pages=Math.max(1,Math.ceil(rows.length/20));page=Math.min(page,pages);output.textContent=JSON.stringify(rows.slice((page-1)*20,page*20),null,2);counter.textContent=' '+page+' / '+pages+' ('+rows.length+')';previous.disabled=page<=1;next.disabled=page>=pages;}\n filter.onchange=()=>{page=1;render();};request.oninput=()=>{page=1;render();};previous.onclick=()=>{page--;render();};next.onclick=()=>{page++;render();};render();\n text('pre',JSON.stringify(history,null,2),section(m.history));text('pre',manifest.missing.join('\\\\n')||'—',section(m.missing));\n} catch {text('p',m.error,document.querySelector('main'));}\n`\n\n/**\n * 包内可信查看器资源,CLI 从内置资源提供代码,绝不执行导入包自带的任意脚本。\n * @example 模块内部上下文\n * ```ts\n * const html = feedbackViewerFiles()['viewer/index.html']\n * const source: string = new TextDecoder().decode(html)\n * ```\n */\nexport function feedbackViewerFiles(): Record<string, Uint8Array> {\n return {\n 'viewer/index.html': strToU8(`<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data:; media-src 'self' blob:; connect-src 'self'; frame-src 'self' about: blob:; font-src 'self' data:; base-uri 'none'; form-action 'none'\"><title>Feedback</title><link rel=\"stylesheet\" href=\"style.css\"></head><body><header></header><main></main><script type=\"module\" src=\"app.js\"></script></body></html>`),\n 'viewer/style.css': strToU8('body{font:15px system-ui,sans-serif;line-height:1.6;margin:0;background:#f5f6f8;color:#172033}header,main{max-width:1080px;margin:auto;padding:24px}section{background:white;padding:24px;margin-bottom:20px;border:1px solid #dce1e8;border-radius:12px}h1{font-size:26px}h2{font-size:19px;margin-top:0}pre{white-space:pre-wrap;overflow-wrap:anywhere;font:13px/1.6 ui-monospace,monospace}img{max-width:100%;height:auto;border:1px solid #dce1e8}figure{margin:16px 0}input,select,button{font:inherit;padding:8px;margin:4px;border:1px solid #ccd2dc;border-radius:6px}button{cursor:pointer}a{display:block;color:#215dc0}'),\n 'viewer/app.js': strToU8(VIEWER_SCRIPT),\n 'viewer/replay.js': strToU8(typeof __FEEDBACK_REPLAY_SCRIPT__ === 'string' ? __FEEDBACK_REPLAY_SCRIPT__ : '/* Replay is available in built package artifacts. */'),\n }\n}\n","import type { HaiResult } from '@h-ai/core'\nimport type { FeedbackBundle, FeedbackBundleEvidence, FeedbackBundleEvidencePage, FeedbackBundleFile, FeedbackBundleInput, FeedbackBundleManifest, FeedbackBundleQuery, FeedbackBundleReadOptions } from './feedback-bundle-types.js'\nimport { core, err, HaiCommonError, ok } from '@h-ai/core'\nimport { strFromU8, strToU8, unzipSync, zipSync } from 'fflate'\nimport { feedbackViewerFiles } from './feedback-bundle-viewer.js'\n\nconst bundleM = core.i18n.createMessageGetter({\n 'zh-CN': { invalid: '问题包格式、容量或完整性校验失败', title: '问题反馈', facts: '采集事实', missing: '缺失材料', user: '用户描述', empty: '未填写', evidence: '证据', attachments: '附件' },\n 'en-US': { invalid: 'The feedback bundle failed format, size or integrity validation', title: 'Feedback report', facts: 'Collected facts', missing: 'Missing evidence', user: 'User description', empty: 'Not provided', evidence: 'Evidence', attachments: 'Attachments' },\n 'ja-JP': { invalid: '問題パッケージの形式、容量または整合性の検証に失敗しました', title: '問題フィードバック', facts: '収集した事実', missing: '不足している資料', user: 'ユーザーの説明', empty: '未入力', evidence: '証拠', attachments: '添付ファイル' },\n})\n\nconst MAX_FILES = 1024\nconst MAX_BYTES = 64 * 1024 * 1024\nconst MAX_FILE_BYTES = 16 * 1024 * 1024\nconst SHA256 = /^[a-f0-9]{64}$/\nconst SAFE_ID = /^[\\w-]{1,128}$/\n\n/** JSON 仅做数据边界,禁止保留原型和可执行值。 */\nfunction jsonObject(value: unknown): Record<string, unknown> {\n const parsed: unknown = JSON.parse(JSON.stringify(value))\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))\n throw new Error('Expected object')\n return parsed as Record<string, unknown>\n}\n\nfunction safePath(value: string): boolean {\n return /^[a-z0-9][\\w./-]*$/i.test(value) && !value.endsWith('/')\n && !value.split('/').some(segment => !segment || segment === '.' || segment === '..')\n}\n\nasync function checksum(bytes: Uint8Array): Promise<string> {\n const hash = await globalThis.crypto.subtle.digest('SHA-256', new Uint8Array(bytes).buffer)\n return Array.from(new Uint8Array(hash), byte => byte.toString(16).padStart(2, '0')).join('')\n}\n\nfunction objectFromFile(files: Readonly<Record<string, Uint8Array>>, path: string): Record<string, unknown> {\n const bytes = files[path]\n if (!bytes)\n throw new Error('Missing file')\n const parsed: unknown = JSON.parse(strFromU8(bytes))\n return jsonObject(parsed)\n}\n\nfunction evidenceFrom(value: unknown): FeedbackBundleEvidence {\n const record = jsonObject(value)\n if (typeof record.source !== 'string' || typeof record.kind !== 'string' || typeof record.timestamp !== 'number' || !Number.isFinite(record.timestamp))\n throw new Error('Invalid evidence')\n for (const key of ['id', 'requestId', 'errorId', 'missingReason']) {\n if (record[key] !== undefined && typeof record[key] !== 'string')\n throw new Error('Invalid evidence field')\n }\n if (record.sequence !== undefined && (typeof record.sequence !== 'number' || !Number.isSafeInteger(record.sequence) || record.sequence < 0))\n throw new Error('Invalid evidence sequence')\n return {\n source: record.source,\n kind: record.kind,\n timestamp: record.timestamp,\n ...(typeof record.id === 'string' ? { id: record.id } : {}),\n ...(typeof record.sequence === 'number' ? { sequence: record.sequence } : {}),\n ...(typeof record.requestId === 'string' ? { requestId: record.requestId } : {}),\n ...(typeof record.errorId === 'string' ? { errorId: record.errorId } : {}),\n ...(typeof record.missingReason === 'string' ? { missingReason: record.missingReason } : {}),\n ...(record.data !== undefined ? { data: record.data } : {}),\n }\n}\n\nfunction limitsFor(options: FeedbackBundleReadOptions = {}) {\n const limits = { maxFiles: options.maxFiles ?? MAX_FILES, maxBytes: options.maxBytes ?? MAX_BYTES, maxFileBytes: options.maxFileBytes ?? MAX_FILE_BYTES }\n if (Object.values(limits).some(value => !Number.isSafeInteger(value) || value <= 0))\n throw new Error('Invalid limits')\n return limits\n}\n\n/** 先检查 ZIP 中央目录,避免解压后才发现重复路径、符号链接或压缩炸弹。 */\nfunction preflight(bytes: Uint8Array, limits: ReturnType<typeof limitsFor>): void {\n if (bytes.byteLength > limits.maxBytes || bytes.byteLength < 22)\n throw new Error('Invalid archive size')\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)\n let end = -1\n for (let offset = bytes.length - 22; offset >= Math.max(0, bytes.length - 65557); offset--) {\n if (view.getUint32(offset, true) === 0x06054B50 && offset + 22 + view.getUint16(offset + 20, true) === bytes.length) {\n end = offset\n break\n }\n }\n if (end < 0 || view.getUint16(end + 4, true) !== 0 || view.getUint16(end + 6, true) !== 0)\n throw new Error('Invalid archive directory')\n const count = view.getUint16(end + 10, true)\n const size = view.getUint32(end + 12, true)\n let offset = view.getUint32(end + 16, true)\n if (count === 0 || count > limits.maxFiles || count !== view.getUint16(end + 8, true) || offset + size !== end)\n throw new Error('Invalid archive directory size')\n let total = 0\n const names = new Set<string>()\n for (let index = 0; index < count; index++) {\n if (offset + 46 > end || view.getUint32(offset, true) !== 0x02014B50)\n throw new Error('Invalid archive entry')\n const nameLength = view.getUint16(offset + 28, true)\n const next = offset + 46 + nameLength + view.getUint16(offset + 30, true) + view.getUint16(offset + 32, true)\n if (next > end)\n throw new Error('Invalid archive entry length')\n const name = strFromU8(bytes.subarray(offset + 46, offset + 46 + nameLength))\n const originalSize = view.getUint32(offset + 24, true)\n const method = view.getUint16(offset + 10, true)\n const mode = (view.getUint32(offset + 38, true) >>> 16) & 0xF000\n if (!safePath(name) || names.has(name.toLowerCase()) || originalSize > limits.maxFileBytes\n || (view.getUint16(offset + 8, true) & 1) || ![0, 8].includes(method) || (mode !== 0 && mode !== 0x8000)) {\n throw new Error('Unsafe archive entry')\n }\n total += originalSize\n if (total > limits.maxBytes)\n throw new Error('Archive expansion limit')\n names.add(name.toLowerCase())\n offset = next\n }\n if (offset !== end)\n throw new Error('Unexpected archive entry')\n}\n\nfunction parseManifest(value: Record<string, unknown>): FeedbackBundleManifest {\n if (value.format !== 'hai-feedback' || value.version !== 1 || typeof value.reportId !== 'string'\n || typeof value.evidenceRevision !== 'number' || !Number.isSafeInteger(value.evidenceRevision) || value.evidenceRevision < 0\n || typeof value.createdAt !== 'number' || !Number.isFinite(value.createdAt) || !Array.isArray(value.files)\n || !Array.isArray(value.missing) || value.missing.some(item => typeof item !== 'string')) {\n throw new Error('Unsupported manifest')\n }\n const files: FeedbackBundleFile[] = value.files.map((item) => {\n const file = jsonObject(item)\n if (typeof file.path !== 'string' || !safePath(file.path) || file.path === 'manifest.json'\n || typeof file.size !== 'number' || !Number.isSafeInteger(file.size) || file.size < 0\n || typeof file.sha256 !== 'string' || !SHA256.test(file.sha256)\n || !['report', 'evidence', 'history', 'attachment', 'summary', 'viewer'].includes(String(file.kind))) {\n throw new Error('Invalid manifest file')\n }\n return {\n path: file.path,\n size: file.size,\n sha256: file.sha256,\n kind: file.kind as FeedbackBundleFile['kind'],\n ...(typeof file.attachmentId === 'string' ? { attachmentId: file.attachmentId } : {}),\n ...(typeof file.name === 'string' ? { name: file.name } : {}),\n ...(typeof file.contentType === 'string' ? { contentType: file.contentType } : {}),\n }\n })\n if (new Set(files.map(file => file.path)).size !== files.length)\n throw new Error('Duplicate manifest file')\n return { format: 'hai-feedback', version: 1, reportId: value.reportId, evidenceRevision: value.evidenceRevision, createdAt: value.createdAt, missing: value.missing as string[], files }\n}\n\n/**\n * 生成含摘要、证据索引、附件与静态查看器的 ZIP;调用前应完成授权与脱敏。\n * @example\n * ```ts\n * import { createFeedbackBundle } from '@h-ai/feedback/bundle'\n * const result = await createFeedbackBundle({ report: { id: 'fb_1' }, evidence: [] })\n * if (result.success) { const bytes: Uint8Array = result.data }\n * ```\n */\nexport async function createFeedbackBundle<TReport extends { id: string }>(input: FeedbackBundleInput<TReport>): Promise<HaiResult<Uint8Array>> {\n try {\n if (input.createdAt !== undefined && (!Number.isSafeInteger(input.createdAt) || input.createdAt < 0))\n throw new Error('Invalid bundle creation time')\n const report = jsonObject(input.report)\n if (typeof report.id !== 'string' || !SAFE_ID.test(report.id))\n throw new Error('Invalid report')\n if (['appVersion', 'buildId', 'commit'].some(key => report[key] !== undefined && (typeof report[key] !== 'string' || report[key].length > 128)))\n throw new Error('Invalid build metadata')\n if (report.evidenceRevision !== undefined && (typeof report.evidenceRevision !== 'number' || !Number.isSafeInteger(report.evidenceRevision) || report.evidenceRevision < 0))\n throw new Error('Invalid evidence revision')\n const evidence = input.evidence.map(evidenceFrom)\n const history = (input.history ?? []).map(jsonObject)\n const missing = [...(input.missing ?? [])]\n if (missing.some(item => typeof item !== 'string'))\n throw new Error('Invalid missing evidence')\n const files: Record<string, Uint8Array> = {\n 'report.json': strToU8(JSON.stringify(report)),\n 'timeline.ndjson': strToU8(evidence.map(record => JSON.stringify(record)).join('\\n')),\n 'evidence/records.json': strToU8(JSON.stringify(evidence)),\n 'evidence/history.json': strToU8(JSON.stringify(history)),\n ...feedbackViewerFiles(),\n }\n const manifestFiles: FeedbackBundleFile[] = []\n const ids = new Set<string>()\n for (const attachment of input.attachments ?? []) {\n if (typeof attachment.id !== 'string' || attachment.id.length < 1 || attachment.id.length > 128 || ids.has(attachment.id) || attachment.bytes.byteLength > MAX_FILE_BYTES)\n throw new Error('Invalid attachment')\n ids.add(attachment.id)\n const suffix = ({ 'image/png': 'png', 'image/jpeg': 'jpg', 'image/webp': 'webp', 'video/webm': 'webm', 'video/mp4': 'mp4', 'text/plain': 'txt', 'application/json': 'json', 'application/pdf': 'pdf' } as Record<string, string>)[attachment.contentType] ?? 'bin'\n // 原始标识仅保存在清单,使用区分大小写且保留字符串编码的摘要构造安全路径。\n const path = `attachments/${await checksum(strToU8(JSON.stringify(attachment.id)))}.${suffix}`\n const hash = await checksum(attachment.bytes)\n if (attachment.checksum && attachment.checksum !== hash)\n throw new Error('Attachment checksum mismatch')\n files[path] = new Uint8Array(attachment.bytes)\n manifestFiles.push({ path, size: attachment.bytes.byteLength, sha256: hash, kind: 'attachment', attachmentId: attachment.id, name: attachment.name, contentType: attachment.contentType })\n }\n const manifest: FeedbackBundleManifest = { format: 'hai-feedback', version: 1, reportId: report.id, evidenceRevision: typeof report.evidenceRevision === 'number' ? report.evidenceRevision : 0, createdAt: input.createdAt ?? Date.now(), missing, files: manifestFiles }\n const temporary: FeedbackBundle = { report: { ...report, id: report.id }, evidence, history, manifest, files }\n files['summary.md'] = strToU8(summarizeFeedbackBundle(temporary))\n for (const [path, bytes] of Object.entries(files)) {\n if (manifestFiles.some(file => file.path === path))\n continue\n const kind = path === 'report.json' ? 'report' : path === 'summary.md' ? 'summary' : path.startsWith('viewer/') ? 'viewer' : path.endsWith('history.json') ? 'history' : 'evidence'\n manifest.files.push({ path, size: bytes.byteLength, sha256: await checksum(bytes), kind })\n }\n files['manifest.json'] = strToU8(JSON.stringify(manifest, null, 2))\n const total = Object.values(files).reduce((sum, file) => sum + file.byteLength, 0)\n if (Object.keys(files).length > MAX_FILES || total > Math.min(input.maxBytes ?? MAX_BYTES, MAX_BYTES) || Object.values(files).some(file => file.byteLength > MAX_FILE_BYTES))\n throw new Error('Bundle limits exceeded')\n return ok(zipSync(files, { level: 1, mtime: new Date(1980, 0, 1) }))\n }\n catch {\n return err(HaiCommonError.VALIDATION_ERROR, bundleM('invalid'))\n }\n}\n\n/**\n * 校验 ZIP 路径、容量、协议与所有文件的 SHA-256 后读取;不执行包内代码。\n * @example\n * ```ts\n * import { createFeedbackBundle, readFeedbackBundle } from '@h-ai/feedback/bundle'\n * const created = await createFeedbackBundle({ report: { id: 'fb_1' }, evidence: [] })\n * if (created.success) {\n * const parsed = await readFeedbackBundle(created.data, { maxBytes: 32 * 1024 * 1024 })\n * if (parsed.success) { const reportId: string = parsed.data.report.id }\n * }\n * ```\n */\nexport async function readFeedbackBundle(bytes: Uint8Array, options?: FeedbackBundleReadOptions): Promise<HaiResult<FeedbackBundle>> {\n try {\n const limits = limitsFor(options)\n preflight(bytes, limits)\n const files = unzipSync(bytes)\n const manifest = parseManifest(objectFromFile(files, 'manifest.json'))\n if (Object.keys(files).length !== manifest.files.length + 1)\n throw new Error('Unexpected bundle file')\n let total = 0\n for (const file of manifest.files) {\n const content = files[file.path]\n if (!content || content.byteLength !== file.size || content.byteLength > limits.maxFileBytes || await checksum(content) !== file.sha256)\n throw new Error('File integrity failed')\n total += content.byteLength\n }\n if (total > limits.maxBytes)\n throw new Error('Bundle expansion limit')\n const report = objectFromFile(files, 'report.json')\n if (['appVersion', 'buildId', 'commit'].some(key => report[key] !== undefined && (typeof report[key] !== 'string' || report[key].length > 128)))\n throw new Error('Invalid build metadata')\n if (typeof report.id !== 'string' || report.id !== manifest.reportId)\n throw new Error('Report mismatch')\n const records: unknown = JSON.parse(strFromU8(files['evidence/records.json'] ?? new Uint8Array()))\n const history: unknown = JSON.parse(strFromU8(files['evidence/history.json'] ?? new Uint8Array()))\n if (!Array.isArray(records) || !Array.isArray(history))\n throw new Error('Invalid evidence collection')\n return ok({ manifest, report: { ...report, id: report.id }, evidence: records.map(evidenceFrom), history: history.map(jsonObject), files })\n }\n catch {\n return err(HaiCommonError.VALIDATION_ERROR, bundleM('invalid'))\n }\n}\n\n/**\n * 生成人工与 AI 可读摘要,明确用户描述与采集事实。\n * @example\n * ```ts\n * import { createFeedbackBundle, readFeedbackBundle, summarizeFeedbackBundle } from '@h-ai/feedback/bundle'\n * const created = await createFeedbackBundle({ report: { id: 'fb_1' }, evidence: [] })\n * if (created.success) {\n * const parsed = await readFeedbackBundle(created.data)\n * if (parsed.success) { const markdown: string = summarizeFeedbackBundle(parsed.data) }\n * }\n * ```\n */\nexport function summarizeFeedbackBundle(bundle: FeedbackBundle): string {\n const report = bundle.report\n const safeText = (value: unknown) => typeof value === 'string' || typeof value === 'number' ? String(value).replaceAll('\\r', '').replaceAll('\\n', ' ') : ''\n return [\n `# ${bundleM('title')} ${safeText(report.number ?? report.id)}`,\n '',\n `## ${bundleM('user')}`,\n '',\n safeText(report.description) || bundleM('empty'),\n '',\n `## ${bundleM('facts')}`,\n '',\n `- ID: ${safeText(report.id)}`,\n `- App: ${safeText(report.appId)} ${safeText(report.appVersion)}`,\n `- Build: ${safeText(report.buildId)} ${safeText(report.commit)}`,\n `- Revision: ${bundle.manifest.evidenceRevision}`,\n `- ${bundleM('evidence')}: ${bundle.evidence.length}`,\n `- ${bundleM('attachments')}: ${bundle.manifest.files.filter(file => file.kind === 'attachment').length}`,\n '',\n `## ${bundleM('missing')}`,\n '',\n ...bundle.manifest.missing.map(reason => `- ${safeText(reason)}`),\n '',\n ].join('\\n')\n}\n\n/**\n * 通过时间、请求、错误与类型筛选离线证据;错误按锚点来源和请求取前后各 100 条,分页最大 100 条。\n * @example\n * ```ts\n * import { createFeedbackBundle, queryFeedbackBundle, readFeedbackBundle } from '@h-ai/feedback/bundle'\n * const created = await createFeedbackBundle({ report: { id: 'fb_1' }, evidence: [] })\n * if (created.success) {\n * const parsed = await readFeedbackBundle(created.data)\n * if (parsed.success) { const page = queryFeedbackBundle(parsed.data, { requestId: 'req_1', page: 1, pageSize: 20 }) }\n * }\n * ```\n */\nexport function queryFeedbackBundle(bundle: FeedbackBundle, query: FeedbackBundleQuery = {}): FeedbackBundleEvidencePage {\n const page = Number.isSafeInteger(query.page) && (query.page ?? 0) > 0 ? query.page! : 1\n const pageSize = Number.isSafeInteger(query.pageSize) && (query.pageSize ?? 0) > 0 ? Math.min(100, query.pageSize!) : 20\n let candidates = bundle.evidence\n if (query.errorId) {\n const anchor = candidates.find(item => item.kind === 'error' && item.errorId === query.errorId)\n if (!anchor)\n return { items: [], total: 0, page, pageSize }\n const anchorSequence = anchor.sequence ?? anchor.timestamp\n const context = candidates.filter(item => item.source === anchor.source && item.requestId === anchor.requestId && ['log', 'error'].includes(item.kind))\n const before = context.filter(item => (item.sequence ?? item.timestamp) <= anchorSequence).sort((left, right) => (right.sequence ?? right.timestamp) - (left.sequence ?? left.timestamp)).slice(0, 101)\n const after = context.filter(item => (item.sequence ?? item.timestamp) > anchorSequence).sort((left, right) => (left.sequence ?? left.timestamp) - (right.sequence ?? right.timestamp)).slice(0, 100)\n const window = new Set([...before, ...after])\n candidates = candidates.filter(item => window.has(item))\n }\n const records = candidates.filter(item => (!query.kind || item.kind === query.kind)\n && (!query.requestId || item.requestId === query.requestId)\n && (query.from === undefined || item.timestamp >= query.from) && (query.to === undefined || item.timestamp <= query.to))\n .sort((left, right) => left.timestamp - right.timestamp || left.source.localeCompare(right.source) || (left.sequence ?? 0) - (right.sequence ?? 0))\n return { items: records.slice((page - 1) * pageSize, page * pageSize), total: records.length, page, pageSize }\n}\n\n/**\n * 获取内置可信离线查看器;本地服务必须使用此资源而不是执行导入包中的脚本。\n * @example\n * ```ts\n * import { getFeedbackBundleViewerFiles } from '@h-ai/feedback/bundle'\n * const html: Uint8Array | undefined = getFeedbackBundleViewerFiles()['viewer/index.html']\n * ```\n */\nexport function getFeedbackBundleViewerFiles(): Readonly<Record<string, Uint8Array>> {\n return feedbackViewerFiles()\n}\n"]}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as FeedbackSaveResult } from '../feedback-client-files-B88nCOo_.js';
|
|
2
2
|
export { F as FeedbackFileSaver, c as configureFeedbackSaving, f as feedbackDataBytes, s as saveFeedbackFile } from '../feedback-client-files-B88nCOo_.js';
|
|
3
|
-
import { FeedbackBudget, FeedbackEvidenceInput, FeedbackSubmitInput, FeedbackReport, FeedbackAppendEvidenceInput, FeedbackCompleteInput, FeedbackUploadInput, FeedbackAttachmentInput, FeedbackDetail, FeedbackEvidenceQuery, FeedbackEvidence, FeedbackExportInput, FeedbackJob, FeedbackAttachmentData } from '@h-ai/api-contract/feedback';
|
|
3
|
+
import { FeedbackBudget, FeedbackEvidenceInput, FeedbackSubmitInput, FeedbackReport, FeedbackAppendEvidenceInput, FeedbackCompleteInput, FeedbackUploadInput, FeedbackAttachmentInput, FeedbackDetail, FeedbackEvidenceQuery, FeedbackEvidence, FeedbackExportInput, FeedbackJob, FeedbackAttachmentData, FeedbackRetryInput } from '@h-ai/api-contract/feedback';
|
|
4
4
|
import * as _h_ai_core from '@h-ai/core';
|
|
5
5
|
import { HaiResult, PaginatedResult } from '@h-ai/core';
|
|
6
6
|
|
|
@@ -80,12 +80,18 @@ interface FeedbackClientConfig {
|
|
|
80
80
|
actions: FeedbackClientActions;
|
|
81
81
|
cookieAllowlist?: readonly string[];
|
|
82
82
|
screenshot?: boolean;
|
|
83
|
-
|
|
83
|
+
/** 自定义截图可响应取消信号;忽略取消时仍等待实际任务退出后才释放单飞锁。 */
|
|
84
|
+
captureScreenshot?: (options?: {
|
|
85
|
+
signal: AbortSignal;
|
|
86
|
+
}) => Promise<Uint8Array>;
|
|
84
87
|
screenshotTimeoutMs?: number;
|
|
88
|
+
/** 自动关键截图显式开启;默认仅在用户准备反馈时截图。 */
|
|
85
89
|
keyScreenshots?: boolean;
|
|
86
90
|
maxKeyScreenshots?: number;
|
|
87
91
|
keyScreenshotThrottleMs?: number;
|
|
88
92
|
replay?: boolean;
|
|
93
|
+
/** 默认 prepare 时启动 DOM 回放;init 模式用于宿主明确需要全程回溯的场景。 */
|
|
94
|
+
replayStart?: 'init' | 'prepare';
|
|
89
95
|
/** 原生 console 采集显式启用;不改变输出级别或默认采集全部 debug。 */
|
|
90
96
|
captureConsole?: boolean;
|
|
91
97
|
persistDraft?: boolean;
|
|
@@ -160,6 +166,8 @@ interface FeedbackBrowserClient {
|
|
|
160
166
|
/** URL 按附件修订复用,删除、替换和会话关闭时释放。 */
|
|
161
167
|
getView: () => FeedbackDraftView;
|
|
162
168
|
releasePreviews: () => void;
|
|
169
|
+
/** 关闭反馈界面时暂停 DOM 回放和截图;保留轻量日志、草稿及已受理上传。 */
|
|
170
|
+
pause: () => void;
|
|
163
171
|
/** 导出并使用当前平台的保存/分享入口。 */
|
|
164
172
|
saveDraft: (input?: FeedbackClientSubmitInput) => Promise<HaiResult<FeedbackSaveResult>>;
|
|
165
173
|
/** 并发初始化返回 INIT_IN_PROGRESS;重初始化先等待旧会话关闭。 */
|
|
@@ -240,6 +248,7 @@ interface FeedbackReviewActions {
|
|
|
240
248
|
id: string;
|
|
241
249
|
jobId: string;
|
|
242
250
|
}) => Promise<HaiResult<FeedbackAttachmentData>>;
|
|
251
|
+
retry: (input: FeedbackRetryInput) => Promise<HaiResult<unknown>>;
|
|
243
252
|
}
|
|
244
253
|
/**
|
|
245
254
|
* 创建可取消的详情动作;退出账号调用 close,旧轮询不能继续下载到新会话。
|
|
@@ -256,6 +265,7 @@ interface FeedbackReviewActions {
|
|
|
256
265
|
*/
|
|
257
266
|
declare function createFeedbackReview(actions: FeedbackReviewActions): {
|
|
258
267
|
close(): void;
|
|
268
|
+
retryReport(id: string): Promise<HaiResult<void>>;
|
|
259
269
|
loadReplay(id: string): Promise<HaiResult<FeedbackEvidence[]>>;
|
|
260
270
|
exportReport(id: string): Promise<{
|
|
261
271
|
success: false;
|