@sleep2agi/commhub-server 0.9.0-preview.13 → 0.9.0-preview.130
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.md +130 -10
- package/bin/commhub.ts +99 -11
- package/package.json +7 -7
- package/src/ack-create-request-transport.test.ts +182 -0
- package/src/ack-create-request.test.ts +50 -7
- package/src/admin-delete-network-http.test.ts +228 -0
- package/src/admin-networks-http.test.ts +88 -0
- package/src/adoption-candidates-http.test.ts +137 -0
- package/src/adoption-candidates.ts +158 -0
- package/src/agent-access-batch.test.ts +210 -0
- package/src/agent-access.ts +487 -0
- package/src/agent-acl-groups-http.test.ts +247 -0
- package/src/agent-acl-http.test.ts +659 -0
- package/src/agent-acl-slice3-http.test.ts +267 -0
- package/src/agent-groups-perf.test.ts +223 -0
- package/src/agent-notice.ts +48 -0
- package/src/agent-team-whoami-http.test.ts +98 -0
- package/src/agent-teams-http.test.ts +323 -0
- package/src/agent-teams.ts +244 -0
- package/src/alias-filter.test.ts +72 -0
- package/src/alias-filter.ts +42 -0
- package/src/api-host-supervisors-can-create-nodes.test.ts +350 -0
- package/src/api-host-supervisors-fallback.test.ts +271 -0
- package/src/api-host-supervisors-runtime-readiness.test.ts +195 -0
- package/src/api-nodes-shape.test.ts +26 -10
- package/src/auth-password-change-cli-tokens-http.test.ts +294 -0
- package/src/auth-sessions-http.test.ts +250 -0
- package/src/auth-validate.test.ts +5 -1
- package/src/auth-weak-password-login.test.ts +176 -0
- package/src/auth.ts +598 -49
- package/src/avatar-validate.test.ts +163 -0
- package/src/avatar-validate.ts +103 -0
- package/src/binding-generation-http.test.ts +121 -0
- package/src/blocked-exit-on-reply.test.ts +122 -0
- package/src/boot-side-effect.test.ts +124 -0
- package/src/clone-node.test.ts +248 -0
- package/src/clone-node.ts +289 -0
- package/src/codex-config-flags.ts +77 -0
- package/src/codex-fork-contract.ts +23 -0
- package/src/codex-fork-migration.test.ts +38 -0
- package/src/commhub-bin-args.test.ts +85 -0
- package/src/config-apply-sec1.test.ts +107 -0
- package/src/config-apply-validate.test.ts +134 -1
- package/src/config-apply-validate.ts +111 -5
- package/src/create-node-blocked-gate.test.ts +356 -0
- package/src/create-node-network-id.test.ts +79 -0
- package/src/create-node-tool-schema.test.ts +48 -0
- package/src/create-node-unicode-name.test.ts +120 -0
- package/src/create-node-validate.test.ts +193 -7
- package/src/create-node-validate.ts +227 -15
- package/src/create-node-workdir.test.ts +289 -0
- package/src/create-node.ts +77 -23
- package/src/daemon-provider-snapshots.test.ts +421 -0
- package/src/daemon-provider-snapshots.ts +327 -0
- package/src/daemon-token-owner-http.test.ts +236 -0
- package/src/dashboard-slash-routing-http.test.ts +107 -0
- package/src/db-adapter-guard.test.ts +148 -0
- package/src/db-adapter-translate.test.ts +128 -0
- package/src/db-adapter.ts +323 -87
- package/src/db-timestamp.test.ts +38 -0
- package/src/db-timestamp.ts +15 -0
- package/src/db.ts +930 -35
- package/src/delivered-without-push.test.ts +140 -0
- package/src/department-groups-http.test.ts +209 -0
- package/src/department-groups-sync-http.test.ts +328 -0
- package/src/department-groups.ts +434 -0
- package/src/department-heads-http.test.ts +330 -0
- package/src/department-heads.ts +226 -0
- package/src/departments-http.test.ts +185 -0
- package/src/departments.ts +260 -0
- package/src/desktop-message-seam.test.ts +150 -0
- package/src/desktop-message.test.ts +368 -0
- package/src/dm-file-scope-http.test.ts +178 -0
- package/src/ext-token-shared.test.ts +60 -0
- package/src/ext-validation-regression.test.ts +131 -0
- package/src/external-schedule-edits-http.test.ts +321 -0
- package/src/external-schedule-edits-timestamps.test.ts +64 -0
- package/src/external-schedule-edits.ts +327 -0
- package/src/file-download-authz-dev-open.test.ts +135 -0
- package/src/file-download-authz.test.ts +404 -0
- package/src/file-download-date-bucket.test.ts +436 -0
- package/src/file-network-scope-dev-open.test.ts +97 -0
- package/src/file-network-scope.test.ts +732 -0
- package/src/group-membership-events-http.test.ts +264 -0
- package/src/group-messages-http.test.ts +482 -0
- package/src/group-messages.ts +297 -0
- package/src/health-redaction.test.ts +642 -0
- package/src/http-gzip.test.ts +94 -0
- package/src/http-gzip.ts +112 -0
- package/src/hub-provider-config.test.ts +139 -0
- package/src/hub-provider-config.ts +105 -0
- package/src/hub-timestamp.test.ts +38 -0
- package/src/hub-timestamp.ts +27 -0
- package/src/human-dm.ts +215 -0
- package/src/inbox-caller.ts +49 -0
- package/src/inbox-count-sse.test.ts +310 -0
- package/src/inbox-count.ts +29 -0
- package/src/index.ts +15 -2362
- package/src/lifecycle-guard.ts +1 -1
- package/src/mcp-context-cost-http.test.ts +123 -0
- package/src/mcp-projects-events-http.test.ts +162 -0
- package/src/mcp-route-label-http.test.ts +120 -0
- package/src/mcp-write-network-resolution.test.ts +432 -0
- package/src/member-audit-network-id-http.test.ts +93 -0
- package/src/member-presence-http.test.ts +210 -0
- package/src/messages-ack-by-agent-http.test.ts +142 -0
- package/src/migrate-sqlite-to-pg.test.ts +47 -0
- package/src/migrate-sqlite-to-pg.ts +263 -0
- package/src/model-auth-notify-http.test.ts +181 -0
- package/src/model-auth-notify.ts +116 -0
- package/src/network-name-http.test.ts +75 -0
- package/src/network-scope.ts +276 -0
- package/src/network-token-membership-http.test.ts +139 -0
- package/src/node-alias-control.ts +12 -0
- package/src/node-attrs-validate.ts +88 -0
- package/src/node-attrs.test.ts +217 -0
- package/src/node-caller-identity-http.test.ts +781 -0
- package/src/node-create-request-status-http.test.ts +105 -0
- package/src/node-daemon-bindings.test.ts +132 -0
- package/src/node-daemon-bindings.ts +130 -0
- package/src/node-files-transport.test.ts +344 -0
- package/src/node-health-codex-login.test.ts +120 -0
- package/src/node-health-dispatch-http.test.ts +279 -0
- package/src/node-health-guard.ts +105 -0
- package/src/node-health-passthrough.test.ts +115 -0
- package/src/node-health-store.ts +101 -0
- package/src/node-id-alias.test.ts +190 -0
- package/src/node-identity-conflict-http.test.ts +269 -0
- package/src/node-identity-conflict.ts +142 -0
- package/src/node-identity-mismatch.test.ts +74 -0
- package/src/node-lifecycle-controllable.test.ts +64 -0
- package/src/node-lifecycle-controllable.ts +53 -0
- package/src/node-lifecycle-read-http.test.ts +245 -0
- package/src/node-lifecycle-read.ts +121 -0
- package/src/node-logs-transport.test.ts +263 -0
- package/src/node-permissions-http.test.ts +276 -0
- package/src/node-permissions.ts +285 -0
- package/src/node-request-retention.test.ts +281 -0
- package/src/node-request-retention.ts +120 -0
- package/src/node-skills-transport.test.ts +209 -0
- package/src/node-token-ownership.ts +51 -0
- package/src/node-token-privilege-http.test.ts +158 -0
- package/src/observer-avatar-http.test.ts +431 -0
- package/src/observer-push.test.ts +252 -0
- package/src/opencode-v2-catalog.test.ts +268 -0
- package/src/opencode-v2-catalog.ts +352 -0
- package/src/package-version-consistency.test.ts +25 -0
- package/src/peer-reply-atomic.test.ts +455 -0
- package/src/peer-reply-capability.test.ts +70 -0
- package/src/pg-worker.ts +87 -0
- package/src/project-dir-warn-clear-http.test.ts +132 -0
- package/src/project-viewer-can-http.test.ts +114 -0
- package/src/push.ts +429 -10
- package/src/redact-tokens.test.ts +65 -0
- package/src/redact-tokens.ts +40 -0
- package/src/rename.ts +22 -2
- package/src/reply-target-derivation.test.ts +96 -0
- package/src/report-status-handover-capabilities-http.test.ts +124 -0
- package/src/report-status-host-schema.test.ts +235 -0
- package/src/report-status-resume-id-handover.test.ts +150 -0
- package/src/require-explicit-test-db.ts +9 -0
- package/src/requirement-due-reminders-http.test.ts +620 -0
- package/src/requirement-due-reminders.ts +416 -0
- package/src/requirement-events-http.test.ts +268 -0
- package/src/requirement-events.test.ts +45 -0
- package/src/requirement-events.ts +144 -0
- package/src/requirement-notify.ts +165 -0
- package/src/requirement-participant-notify-http.test.ts +309 -0
- package/src/requirement-tag-ops-http.test.ts +266 -0
- package/src/requirement-tags.ts +89 -0
- package/src/requirements-abandoned-http.test.ts +236 -0
- package/src/requirements-comment-http.test.ts +167 -0
- package/src/requirements-completed-at-migration.test.ts +70 -0
- package/src/requirements-errors-mcp-http.test.ts +164 -0
- package/src/requirements-errors.ts +143 -0
- package/src/requirements-etag-http.test.ts +91 -0
- package/src/requirements-http.test.ts +709 -0
- package/src/requirements-last-event-http.test.ts +349 -0
- package/src/requirements-list-mcp-slim-http.test.ts +155 -0
- package/src/requirements-list-slim-http.test.ts +319 -0
- package/src/requirements-mcp.test.ts +163 -0
- package/src/requirements-migrate.ts +259 -0
- package/src/requirements-owner-strict-mcp-http.test.ts +145 -0
- package/src/requirements-people-http.test.ts +114 -0
- package/src/requirements-people-mcp-http.test.ts +225 -0
- package/src/requirements-people.ts +127 -0
- package/src/requirements-priority-lowest.test.ts +114 -0
- package/src/requirements-search-http.test.ts +182 -0
- package/src/requirements-search.test.ts +32 -0
- package/src/requirements-search.ts +145 -0
- package/src/requirements-seq-http.test.ts +193 -0
- package/src/requirements-seq-migration.test.ts +63 -0
- package/src/requirements-start-date.test.ts +125 -0
- package/src/requirements-stats-http.test.ts +288 -0
- package/src/requirements-stats.ts +167 -0
- package/src/requirements-status-participants-http.test.ts +222 -0
- package/src/requirements-upgrade-e2e.test.ts +126 -0
- package/src/requirements-upgrade-migration.test.ts +98 -0
- package/src/requirements.ts +1806 -0
- package/src/resolve-port.test.ts +57 -0
- package/src/resolve-port.ts +44 -0
- package/src/response-charset.test.ts +130 -0
- package/src/rest-explicit-columns-http.test.ts +222 -0
- package/src/rest-identity.test.ts +129 -0
- package/src/rest-identity.ts +125 -0
- package/src/rest-projections.ts +68 -0
- package/src/rest-write-network-resolution.test.ts +133 -0
- package/src/restricted-files.ts +76 -0
- package/src/retention.ts +15 -3
- package/src/route-callers-http.test.ts +120 -0
- package/src/route-timing.test.ts +211 -0
- package/src/route-timing.ts +258 -0
- package/src/rules-file-duplicate-alias.test.ts +234 -0
- package/src/rules-file-session-target.test.ts +207 -0
- package/src/rules-file-transport.test.ts +237 -0
- package/src/runtime-label.test.ts +22 -0
- package/src/runtime-label.ts +21 -0
- package/src/runtime-readiness.ts +90 -0
- package/src/schedule-agent-mcp-http.test.ts +381 -0
- package/src/schedule-agent.ts +192 -0
- package/src/scheduled-doorbell-nodeless-session.test.ts +134 -0
- package/src/scheduled-failures-http.test.ts +233 -0
- package/src/scheduled-failures.ts +179 -0
- package/src/scheduled-offline.test.ts +131 -0
- package/src/scheduled-offline.ts +66 -0
- package/src/scheduled-reply-unread-http.test.ts +212 -0
- package/src/scheduled-run-terminal.test.ts +268 -0
- package/src/scheduled-skip-blocking-task.test.ts +120 -0
- package/src/scheduled-stuck-http.test.ts +238 -0
- package/src/scheduled-stuck.ts +153 -0
- package/src/scheduled-tasks-http.test.ts +638 -0
- package/src/scheduled-tasks.ts +766 -0
- package/src/scoped-secrets.ts +124 -0
- package/src/send-dedup-request-id.test.ts +229 -0
- package/src/send-reply-agent-warning.test.ts +202 -0
- package/src/send-reply-alias-optional.test.ts +131 -0
- package/src/send-reply-attachments.test.ts +458 -0
- package/src/send_dedup.test.ts +54 -0
- package/src/send_dedup.ts +52 -6
- package/src/serve-error.test.ts +38 -0
- package/src/serve-error.ts +27 -0
- package/src/server.ts +5257 -0
- package/src/session-task-on-dispatch.ts +14 -0
- package/src/session-token-ownership-http.test.ts +122 -0
- package/src/shared/codex-inventory.ts +41 -0
- package/src/shared/daemon-provider-login.ts +31 -0
- package/src/shared/daemon-provider-snapshot.ts +52 -0
- package/src/shared/external-schedule-contract.ts +60 -0
- package/src/shared/node-name.test.ts +62 -0
- package/src/shared/node-name.ts +161 -0
- package/src/shared/provider-application.ts +29 -0
- package/src/shared/provider-collection.ts +14 -0
- package/src/shared/provider-initialization.ts +8 -0
- package/src/shared/reserved-env-drift.test.ts +10 -0
- package/src/shared/scoped-secret-contract.ts +5 -0
- package/src/shared/task-content-limit-drift.test.ts +28 -0
- package/src/shared/task-content-limit.ts +25 -0
- package/src/side-thread-command-transport.test.ts +312 -0
- package/src/side-thread-command-transport.ts +318 -0
- package/src/side-thread-http-integration.test.ts +233 -0
- package/src/side-thread-http.ts +504 -0
- package/src/side-thread-production.test.ts +72 -0
- package/src/side-thread-production.ts +118 -0
- package/src/side-thread.test.ts +1067 -0
- package/src/side-thread.ts +2499 -0
- package/src/skillhub-http.test.ts +108 -0
- package/src/sqlite-close-checkpoint.test.ts +78 -0
- package/src/start-node.test.ts +264 -0
- package/src/status-alias-filter-http.test.ts +106 -0
- package/src/status-alias-resolver-http.test.ts +163 -0
- package/src/status-alias-resolver.ts +19 -0
- package/src/status-node-id-filter-http.test.ts +161 -0
- package/src/status-read-cache-http.test.ts +229 -0
- package/src/status-read-cache.ts +122 -0
- package/src/stop-delete-node.test.ts +509 -1
- package/src/task-access-http.test.ts +402 -0
- package/src/task-access.ts +420 -0
- package/src/task-auth-origin-http.test.ts +91 -0
- package/src/task-auth-origin.test.ts +24 -0
- package/src/task-auth-origin.ts +7 -0
- package/src/task-consumption.test.ts +482 -0
- package/src/task-diagnostic-http.test.ts +149 -0
- package/src/task-diagnostic.test.ts +115 -0
- package/src/task-diagnostic.ts +118 -0
- package/src/task-expiry-notice-http.test.ts +287 -0
- package/src/task-expiry-notice.ts +315 -0
- package/src/task-idempotency-mcp.test.ts +142 -0
- package/src/task-idempotency.test.ts +60 -0
- package/src/task-idempotency.ts +75 -0
- package/src/task-late-replies.ts +54 -0
- package/src/task-late-reply-http.test.ts +152 -0
- package/src/task-lifecycle-watcher.test.ts +204 -0
- package/src/task-lifecycle-watcher.ts +49 -0
- package/src/task-network-resolution-http.test.ts +141 -0
- package/src/task-orphan-http.test.ts +287 -0
- package/src/task-orphan.ts +119 -0
- package/src/task-queue-ahead-http.test.ts +309 -0
- package/src/task-queue-ahead-scale.test.ts +236 -0
- package/src/task-queue-ahead.ts +186 -0
- package/src/task-rejection-log-http.test.ts +160 -0
- package/src/task-rejection-log.ts +49 -0
- package/src/task-stale-open-http.test.ts +290 -0
- package/src/task-stale-open.ts +103 -0
- package/src/tasks-pagination-http.test.ts +141 -0
- package/src/tool-audience-http.test.ts +220 -0
- package/src/tool-audience.ts +118 -0
- package/src/tools-list-json-schema.test.ts +67 -0
- package/src/tools.ts +3927 -323
- package/src/upload-early-reject-guard.test.ts +155 -0
- package/src/uploads-http.test.ts +97 -15
- package/src/uploads.ts +103 -6
- package/src/user-avatar-http.test.ts +74 -0
- package/src/user-avatar-migration.test.ts +25 -0
- package/src/user-inbox-by-agent-http.test.ts +161 -0
- package/src/user-inbox-netid-notnull.test.ts +122 -0
- package/src/user-inbox-read-http.test.ts +223 -0
- package/src/user-inbox-schema.test.ts +54 -0
package/src/send_dedup.ts
CHANGED
|
@@ -33,6 +33,26 @@
|
|
|
33
33
|
// - The structured response shape is identical between MCP and REST
|
|
34
34
|
// callers so the LLM gets the same parseable hint regardless of
|
|
35
35
|
// transport.
|
|
36
|
+
//
|
|
37
|
+
// Key selection (#2043, follow-up to #212): the content-hash key above is
|
|
38
|
+
// only right for callers that have no other notion of "one message".
|
|
39
|
+
// Human chat clients (the desktop/mobile app, the web Dashboard) mint a
|
|
40
|
+
// fresh `meta.client_request_id` per message bubble and reuse it only
|
|
41
|
+
// when retrying that same bubble. For those sends the content hash is the
|
|
42
|
+
// WRONG identity: a user who types 「好」 twice within five minutes sent
|
|
43
|
+
// two messages, and dropping the second one (while the app maps the 429
|
|
44
|
+
// to "delivered") silently loses it. So:
|
|
45
|
+
// - a send WITH a valid client_request_id is keyed on
|
|
46
|
+
// (network, from, to, client_request_id) — a new id always proceeds,
|
|
47
|
+
// identical text or not; a same-id retry is answered from the durable
|
|
48
|
+
// tasks row by the handler (task-idempotency.ts) before this index is
|
|
49
|
+
// even consulted, so this key is defence in depth, not the primary
|
|
50
|
+
// replay mechanism;
|
|
51
|
+
// - a send WITHOUT one (agent / MCP / scripted traffic) keeps the #212
|
|
52
|
+
// content-hash window unchanged.
|
|
53
|
+
// One id is one message: re-using an id with different content is a
|
|
54
|
+
// client bug and the handlers fail it closed (`idempotency_conflict`)
|
|
55
|
+
// rather than storing a second row or silently replaying the first.
|
|
36
56
|
|
|
37
57
|
import { createHash } from "crypto";
|
|
38
58
|
|
|
@@ -61,9 +81,23 @@ export function readDedupConfig(env: NodeJS.ProcessEnv = process.env): DedupConf
|
|
|
61
81
|
};
|
|
62
82
|
}
|
|
63
83
|
|
|
84
|
+
/** Which identity a dedup key was built from. Logged on every drop. */
|
|
85
|
+
export type DedupKeyKind = "request_id" | "content";
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Optional per-send scope. When `requestId` is a non-empty string the key
|
|
89
|
+
* is the request id (scoped by network/from/to) instead of the content hash.
|
|
90
|
+
* Callers must pass the already-validated id (clientRequestIdFromMeta), not
|
|
91
|
+
* raw client input.
|
|
92
|
+
*/
|
|
93
|
+
export type DedupScope = {
|
|
94
|
+
requestId?: string | null;
|
|
95
|
+
networkId?: string | null;
|
|
96
|
+
};
|
|
97
|
+
|
|
64
98
|
export type DedupCheck =
|
|
65
99
|
| { duplicate: false }
|
|
66
|
-
| { duplicate: true; lastSentMs: number; ageMs: number };
|
|
100
|
+
| { duplicate: true; lastSentMs: number; ageMs: number; kind: DedupKeyKind };
|
|
67
101
|
|
|
68
102
|
export class SendDedup {
|
|
69
103
|
private last = new Map<string, number>();
|
|
@@ -93,6 +127,18 @@ export class SendDedup {
|
|
|
93
127
|
return `${from}|${to}|${hash}`;
|
|
94
128
|
}
|
|
95
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Pick the dedup identity for one send. A validated client request id
|
|
132
|
+
* wins over the content hash; see the header comment for why.
|
|
133
|
+
*/
|
|
134
|
+
static keyFor(from: string, to: string, content: string, scope: DedupScope = {}): { key: string; kind: DedupKeyKind } {
|
|
135
|
+
const requestId = typeof scope.requestId === "string" && scope.requestId.length > 0 ? scope.requestId : null;
|
|
136
|
+
if (requestId) {
|
|
137
|
+
return { key: `rid|${scope.networkId ?? ""}|${from}|${to}|${requestId}`, kind: "request_id" };
|
|
138
|
+
}
|
|
139
|
+
return { key: SendDedup.key(from, to, content), kind: "content" };
|
|
140
|
+
}
|
|
141
|
+
|
|
96
142
|
/**
|
|
97
143
|
* Check whether (from, to, content) was sent recently. Returns
|
|
98
144
|
* `{ duplicate: false }` when the call should proceed, or
|
|
@@ -103,10 +149,10 @@ export class SendDedup {
|
|
|
103
149
|
* AFTER the underlying side effect (inbox insert + pushEvent) succeeds,
|
|
104
150
|
* so failed sends don't accidentally block legitimate retries.
|
|
105
151
|
*/
|
|
106
|
-
check(from: string, to: string, content: string, nowMs: number = Date.now()): DedupCheck {
|
|
152
|
+
check(from: string, to: string, content: string, nowMs: number = Date.now(), scope: DedupScope = {}): DedupCheck {
|
|
107
153
|
if (!this.enabled) return { duplicate: false };
|
|
108
154
|
this.evictExpired(nowMs);
|
|
109
|
-
const k = SendDedup.
|
|
155
|
+
const { key: k, kind } = SendDedup.keyFor(from, to, content, scope);
|
|
110
156
|
const lastSentMs = this.last.get(k);
|
|
111
157
|
if (lastSentMs === undefined) return { duplicate: false };
|
|
112
158
|
const ageMs = nowMs - lastSentMs;
|
|
@@ -114,13 +160,13 @@ export class SendDedup {
|
|
|
114
160
|
this.last.delete(k);
|
|
115
161
|
return { duplicate: false };
|
|
116
162
|
}
|
|
117
|
-
return { duplicate: true, lastSentMs, ageMs };
|
|
163
|
+
return { duplicate: true, lastSentMs, ageMs, kind };
|
|
118
164
|
}
|
|
119
165
|
|
|
120
166
|
/** Record a successful send for future dedup checks. */
|
|
121
|
-
record(from: string, to: string, content: string, nowMs: number = Date.now()): void {
|
|
167
|
+
record(from: string, to: string, content: string, nowMs: number = Date.now(), scope: DedupScope = {}): void {
|
|
122
168
|
if (!this.enabled) return;
|
|
123
|
-
const k = SendDedup.
|
|
169
|
+
const k = SendDedup.keyFor(from, to, content, scope).key;
|
|
124
170
|
this.last.set(k, nowMs);
|
|
125
171
|
if (this.last.size > this.cfg.maxKeys) this.evictOldest();
|
|
126
172
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { buildServeErrorResponse, serveErrorBody } from "./serve-error.js";
|
|
3
|
+
|
|
4
|
+
// 注:「没有 error 回调时 Bun 回默认非 JSON 500」这一半没法在 bun test 里当用例:
|
|
5
|
+
// 运行器会把 fetch 里抛出的异常当成未处理错误直接判这条用例失败(实测),证据在 #695 末评。
|
|
6
|
+
describe("#695 fetch exceptions become JSON with the exception's own message", () => {
|
|
7
|
+
test("body carries THE message, both JSON-RPC-shaped and REST-shaped", () => {
|
|
8
|
+
const b = serveErrorBody(new Error("boom-695: sqlite is locked"));
|
|
9
|
+
expect(b.error.code).toBe(-32603);
|
|
10
|
+
expect(b.error.message).toBe("boom-695: sqlite is locked");
|
|
11
|
+
expect(b.message).toBe("boom-695: sqlite is locked");
|
|
12
|
+
expect(b.ok).toBe(false);
|
|
13
|
+
// 不是固定 JSON:换一个异常,message 跟着变
|
|
14
|
+
expect(serveErrorBody(new Error("other")).error.message).toBe("other");
|
|
15
|
+
expect(serveErrorBody("plain string").error.message).toBe("plain string");
|
|
16
|
+
expect(serveErrorBody(new Error("")).error.message).toBe("internal error");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("real Bun.serve: a throwing fetch answers 500 + application/json + the message (was a non-JSON default page)", async () => {
|
|
20
|
+
const server = Bun.serve({
|
|
21
|
+
port: 0,
|
|
22
|
+
fetch() { throw new Error("boom-695-live"); },
|
|
23
|
+
error: (err) => buildServeErrorResponse(err),
|
|
24
|
+
});
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetch(`http://127.0.0.1:${server.port}/mcp`, { method: "POST", body: "{}" });
|
|
27
|
+
expect(res.status).toBe(500);
|
|
28
|
+
expect(res.headers.get("content-type") ?? "").toContain("application/json");
|
|
29
|
+
const json = await res.json() as { error: { code: number; message: string }; ok: boolean };
|
|
30
|
+
expect(json.error.code).toBe(-32603);
|
|
31
|
+
expect(json.error.message).toBe("boom-695-live");
|
|
32
|
+
expect(json.ok).toBe(false);
|
|
33
|
+
} finally {
|
|
34
|
+
server.stop(true);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// #695 —— Bun.serve 没注册 `error` 回调时,fetch 处理器抛出的异常会变成 Bun 的默认 500,
|
|
2
|
+
// body 不是 JSON;MCP 客户端于是只能报一个泛化的 -32603,拿不到任何一句人话。
|
|
3
|
+
// 这里把异常变成一个带 message 的 JSON 响应:/mcp 的客户端按 JSON-RPC 形状读 error.message,
|
|
4
|
+
// REST 客户端按本仓其它路由的 { ok:false, error, message } 形状读。
|
|
5
|
+
// 🔴 判据(#695 末评):同一个异常,响应里要带**那个异常的 message**,不是一个固定 JSON。
|
|
6
|
+
|
|
7
|
+
export interface ServeErrorBody {
|
|
8
|
+
jsonrpc: "2.0";
|
|
9
|
+
id: null;
|
|
10
|
+
error: { code: -32603; message: string };
|
|
11
|
+
ok: false;
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function serveErrorBody(err: unknown): ServeErrorBody {
|
|
16
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
17
|
+
const message = (raw && raw.trim()) ? raw.trim().slice(0, 500) : "internal error";
|
|
18
|
+
return { jsonrpc: "2.0", id: null, error: { code: -32603, message }, ok: false, message };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function buildServeErrorResponse(err: unknown): Response {
|
|
22
|
+
const body = serveErrorBody(err);
|
|
23
|
+
return new Response(JSON.stringify(body), {
|
|
24
|
+
status: 500,
|
|
25
|
+
headers: { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" },
|
|
26
|
+
});
|
|
27
|
+
}
|