borgmcp-shared 0.12.3 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/RELEASES.md +16 -0
- package/dist/conformance/adapter.d.ts +7 -0
- package/dist/conformance/adapter.d.ts.map +1 -1
- package/dist/conformance/adapter.js +157 -3
- package/dist/conformance/adapter.js.map +1 -1
- package/dist/conformance/index.d.ts +33 -0
- package/dist/conformance/index.d.ts.map +1 -1
- package/dist/conformance/index.js +10 -0
- package/dist/conformance/index.js.map +1 -1
- package/dist/protocol/contract.d.ts +36 -2
- package/dist/protocol/contract.d.ts.map +1 -1
- package/dist/protocol/contract.js +19 -5
- package/dist/protocol/contract.js.map +1 -1
- package/dist/protocol/coordination.d.ts.map +1 -1
- package/dist/protocol/coordination.js +11 -2
- package/dist/protocol/coordination.js.map +1 -1
- package/dist/protocol/documents.d.ts +78 -0
- package/dist/protocol/documents.d.ts.map +1 -0
- package/dist/protocol/documents.js +196 -0
- package/dist/protocol/documents.js.map +1 -0
- package/dist/protocol/errors.d.ts +5 -0
- package/dist/protocol/errors.d.ts.map +1 -1
- package/dist/protocol/errors.js +5 -0
- package/dist/protocol/errors.js.map +1 -1
- package/dist/protocol/index.d.ts +1 -0
- package/dist/protocol/index.d.ts.map +1 -1
- package/dist/protocol/index.js +1 -0
- package/dist/protocol/index.js.map +1 -1
- package/dist/protocol/sse.d.ts +2 -2
- package/dist/protocol/sse.d.ts.map +1 -1
- package/dist/protocol/sse.js +33 -4
- package/dist/protocol/sse.js.map +1 -1
- package/dist/protocol/types.d.ts +7 -0
- package/dist/protocol/types.d.ts.map +1 -1
- package/dist/protocol/version.d.ts +1 -1
- package/dist/protocol/version.d.ts.map +1 -1
- package/dist/protocol/version.js +1 -1
- package/dist/protocol/version.js.map +1 -1
- package/docs/compatibility.md +5 -0
- package/docs/cube-documents.md +35 -0
- package/docs/release-records.json +15 -0
- package/docs/releases/0.13.0.md +7 -0
- package/package.json +1 -1
- package/src/conformance/adapter.ts +340 -1
- package/src/conformance/index.ts +11 -0
- package/src/protocol/contract.ts +19 -5
- package/src/protocol/coordination.ts +15 -1
- package/src/protocol/documents.ts +256 -0
- package/src/protocol/errors.ts +5 -0
- package/src/protocol/index.ts +1 -0
- package/src/protocol/sse.ts +38 -3
- package/src/protocol/types.ts +7 -0
- package/src/protocol/version.ts +2 -2
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { ErrorCode } from './errors.js';
|
|
2
|
+
import {
|
|
3
|
+
ProtocolContractError,
|
|
4
|
+
decodeCanonicalTimestamp,
|
|
5
|
+
decodeOpaqueIdentifier,
|
|
6
|
+
decodeProtocolEnvelope,
|
|
7
|
+
decodeUuid,
|
|
8
|
+
utf8ByteLength,
|
|
9
|
+
type ProtocolEnvelope,
|
|
10
|
+
} from './contract.js';
|
|
11
|
+
|
|
12
|
+
export const DOCUMENT_CONTENT_TYPES = ['text/markdown', 'text/plain'] as const;
|
|
13
|
+
export const DOCUMENT_DEFAULT_MAX_BYTES = 65_536 as const;
|
|
14
|
+
export const DOCUMENT_DEFAULT_MAX_ACTIVE_BYTES_PER_CUBE = 524_288 as const;
|
|
15
|
+
export const DOCUMENT_MAX_BYTES_ENV = 'BORG_SERVER_MAX_DOCUMENT_BYTES' as const;
|
|
16
|
+
export const DOCUMENT_MAX_ACTIVE_BYTES_PER_CUBE_ENV =
|
|
17
|
+
'BORG_SERVER_MAX_ACTIVE_DOCUMENT_BYTES_PER_CUBE' as const;
|
|
18
|
+
export type DocumentContentType = (typeof DOCUMENT_CONTENT_TYPES)[number];
|
|
19
|
+
export type DocumentState = 'active' | 'superseded' | 'removed';
|
|
20
|
+
|
|
21
|
+
export interface DocumentActor {
|
|
22
|
+
drone_id: string | null;
|
|
23
|
+
label: string | null;
|
|
24
|
+
role: string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface DocumentCitation {
|
|
28
|
+
id: string;
|
|
29
|
+
title: string;
|
|
30
|
+
size_bytes: number;
|
|
31
|
+
state: DocumentState;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CubeDocumentMetadata extends DocumentCitation {
|
|
35
|
+
content_type: DocumentContentType;
|
|
36
|
+
supersedes: string | null;
|
|
37
|
+
superseded_by: string | null;
|
|
38
|
+
author: DocumentActor;
|
|
39
|
+
created_at: string;
|
|
40
|
+
removed_by: DocumentActor | null;
|
|
41
|
+
removed_at: string | null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface CubeDocument extends CubeDocumentMetadata {
|
|
45
|
+
content: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface PutDocumentRequest {
|
|
49
|
+
title: string;
|
|
50
|
+
content_type: DocumentContentType;
|
|
51
|
+
content: string;
|
|
52
|
+
supersedes?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface PutDocumentResult { document: CubeDocument }
|
|
55
|
+
export interface GetDocumentRequest { id: string }
|
|
56
|
+
export interface GetDocumentResult { document: CubeDocument }
|
|
57
|
+
export type ListDocumentsRequest = Record<string, never>;
|
|
58
|
+
export interface ListDocumentsResult { documents: CubeDocumentMetadata[] }
|
|
59
|
+
export interface RemoveDocumentRequest { id: string }
|
|
60
|
+
export interface RemoveDocumentResult { document: CubeDocumentMetadata }
|
|
61
|
+
|
|
62
|
+
function object(value: unknown): Record<string, unknown> {
|
|
63
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
64
|
+
throw new ProtocolContractError('Expected a document object.');
|
|
65
|
+
}
|
|
66
|
+
return value as Record<string, unknown>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function exact(value: Record<string, unknown>, allowed: readonly string[], required: readonly string[]): void {
|
|
70
|
+
for (const key of Object.keys(value)) {
|
|
71
|
+
if (!allowed.includes(key)) throw new ProtocolContractError('Unknown document field.');
|
|
72
|
+
}
|
|
73
|
+
for (const key of required) {
|
|
74
|
+
if (!Object.prototype.hasOwnProperty.call(value, key)) {
|
|
75
|
+
throw new ProtocolContractError(`Missing document field "${key}".`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function text(value: unknown, field: string, maximumBytes: number, allowEmpty = false): string {
|
|
81
|
+
if (typeof value !== 'string' || (!allowEmpty && value.length === 0) || utf8ByteLength(value) > maximumBytes) {
|
|
82
|
+
throw new ProtocolContractError(`Invalid document field "${field}".`);
|
|
83
|
+
}
|
|
84
|
+
for (let index = 0; index < value.length; index++) {
|
|
85
|
+
const code = value.charCodeAt(index);
|
|
86
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
87
|
+
const next = value.charCodeAt(index + 1);
|
|
88
|
+
if (!(next >= 0xdc00 && next <= 0xdfff)) throw new ProtocolContractError(`Invalid UTF-8 document field "${field}".`);
|
|
89
|
+
index++;
|
|
90
|
+
} else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
91
|
+
throw new ProtocolContractError(`Invalid UTF-8 document field "${field}".`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function title(value: unknown): string {
|
|
98
|
+
const decoded = text(value, 'title', 480);
|
|
99
|
+
if (Array.from(decoded).length > 120 || decoded !== decoded.trim() || /[\u0000-\u001f\u007f-\u009f]/.test(decoded)) {
|
|
100
|
+
throw new ProtocolContractError('Invalid document field "title".');
|
|
101
|
+
}
|
|
102
|
+
return decoded;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function contentType(value: unknown): DocumentContentType {
|
|
106
|
+
if (!DOCUMENT_CONTENT_TYPES.includes(value as DocumentContentType)) {
|
|
107
|
+
throw new ProtocolContractError(
|
|
108
|
+
'Unsupported document content type.',
|
|
109
|
+
ErrorCode.DOCUMENT_CONTENT_TYPE_UNSUPPORTED,
|
|
110
|
+
['content_type'],
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return value as DocumentContentType;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function count(value: unknown, field: string): number {
|
|
117
|
+
if (!Number.isSafeInteger(value) || (value as number) < 0 || (value as number) > 10 * 1024 * 1024) {
|
|
118
|
+
throw new ProtocolContractError(`Invalid document field "${field}".`);
|
|
119
|
+
}
|
|
120
|
+
return value as number;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function nullableId(value: unknown, field: string): string | null {
|
|
124
|
+
return value === null ? null : decodeOpaqueIdentifier(value, [field]);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function nullableText(value: unknown, field: string): string | null {
|
|
128
|
+
return value === null ? null : text(value, field, 120);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function decodeDocumentActor(value: unknown): DocumentActor {
|
|
132
|
+
const input = object(value);
|
|
133
|
+
exact(input, ['drone_id', 'label', 'role'], ['drone_id', 'label', 'role']);
|
|
134
|
+
return {
|
|
135
|
+
drone_id: input.drone_id === null ? null : decodeUuid(input.drone_id, ['drone_id']),
|
|
136
|
+
label: nullableText(input.label, 'label'),
|
|
137
|
+
role: nullableText(input.role, 'role'),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function decodeDocumentCitation(value: unknown): DocumentCitation {
|
|
142
|
+
const input = object(value);
|
|
143
|
+
exact(input, ['id', 'title', 'size_bytes', 'state'], ['id', 'title', 'size_bytes', 'state']);
|
|
144
|
+
if (!['active', 'superseded', 'removed'].includes(String(input.state))) {
|
|
145
|
+
throw new ProtocolContractError('Invalid document state.');
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
id: decodeOpaqueIdentifier(input.id, ['id']),
|
|
149
|
+
title: title(input.title),
|
|
150
|
+
size_bytes: count(input.size_bytes, 'size_bytes'),
|
|
151
|
+
state: input.state as DocumentState,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function decodeDocumentCitations(value: unknown): DocumentCitation[] {
|
|
156
|
+
if (!Array.isArray(value) || value.length < 1 || value.length > 100) {
|
|
157
|
+
throw new ProtocolContractError('Document citations must contain 1-100 entries.');
|
|
158
|
+
}
|
|
159
|
+
const citations = value.map(decodeDocumentCitation);
|
|
160
|
+
if (new Set(citations.map(({ id }) => id)).size !== citations.length) {
|
|
161
|
+
throw new ProtocolContractError('Document citation ids must be unique.');
|
|
162
|
+
}
|
|
163
|
+
return citations;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function decodeCubeDocumentMetadata(value: unknown): CubeDocumentMetadata {
|
|
167
|
+
const input = object(value);
|
|
168
|
+
exact(input, ['id', 'title', 'size_bytes', 'state', 'content_type', 'supersedes', 'superseded_by', 'author', 'created_at', 'removed_by', 'removed_at'], ['id', 'title', 'size_bytes', 'state', 'content_type', 'supersedes', 'superseded_by', 'author', 'created_at', 'removed_by', 'removed_at']);
|
|
169
|
+
const citation = decodeDocumentCitation({ id: input.id, title: input.title, size_bytes: input.size_bytes, state: input.state });
|
|
170
|
+
const removed = citation.state === 'removed';
|
|
171
|
+
const hasRemovedBy = input.removed_by !== null;
|
|
172
|
+
const hasRemovedAt = input.removed_at !== null;
|
|
173
|
+
if (hasRemovedBy !== hasRemovedAt || removed !== hasRemovedBy) {
|
|
174
|
+
throw new ProtocolContractError('Removed document audit fields do not match its state.');
|
|
175
|
+
}
|
|
176
|
+
if (citation.state === 'active' && input.superseded_by !== null) {
|
|
177
|
+
throw new ProtocolContractError('Active document cannot have a superseding revision.');
|
|
178
|
+
}
|
|
179
|
+
if (citation.state === 'superseded' && input.superseded_by === null) {
|
|
180
|
+
throw new ProtocolContractError('Superseded document must identify its next revision.');
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
...citation,
|
|
184
|
+
content_type: contentType(input.content_type),
|
|
185
|
+
supersedes: nullableId(input.supersedes, 'supersedes'),
|
|
186
|
+
superseded_by: nullableId(input.superseded_by, 'superseded_by'),
|
|
187
|
+
author: decodeDocumentActor(input.author),
|
|
188
|
+
created_at: decodeCanonicalTimestamp(input.created_at, ['created_at']),
|
|
189
|
+
removed_by: input.removed_by === null ? null : decodeDocumentActor(input.removed_by),
|
|
190
|
+
removed_at: input.removed_at === null ? null : decodeCanonicalTimestamp(input.removed_at, ['removed_at']),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function decodeCubeDocument(value: unknown): CubeDocument {
|
|
195
|
+
const input = object(value);
|
|
196
|
+
const content = text(input.content, 'content', 10 * 1024 * 1024, true);
|
|
197
|
+
const { content: _content, ...metadataInput } = input;
|
|
198
|
+
const metadata = decodeCubeDocumentMetadata(metadataInput);
|
|
199
|
+
if (metadata.size_bytes !== utf8ByteLength(content)) throw new ProtocolContractError('Document size does not match its UTF-8 content.');
|
|
200
|
+
return { ...metadata, content };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function decodePutDocumentRequest(value: unknown): PutDocumentRequest {
|
|
204
|
+
const input = object(value);
|
|
205
|
+
exact(input, ['title', 'content_type', 'content', 'supersedes'], ['title', 'content_type', 'content']);
|
|
206
|
+
const output: PutDocumentRequest = {
|
|
207
|
+
title: title(input.title),
|
|
208
|
+
content_type: contentType(input.content_type),
|
|
209
|
+
content: text(input.content, 'content', 10 * 1024 * 1024, true),
|
|
210
|
+
};
|
|
211
|
+
if (input.supersedes !== undefined) output.supersedes = decodeOpaqueIdentifier(input.supersedes, ['supersedes']);
|
|
212
|
+
return output;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function decodeGetDocumentRequest(value: unknown): GetDocumentRequest {
|
|
216
|
+
const input = object(value); exact(input, ['id'], ['id']);
|
|
217
|
+
return { id: decodeOpaqueIdentifier(input.id, ['id']) };
|
|
218
|
+
}
|
|
219
|
+
export function decodeListDocumentsRequest(value: unknown): ListDocumentsRequest {
|
|
220
|
+
const input = object(value); exact(input, [], []); return {};
|
|
221
|
+
}
|
|
222
|
+
export const decodeRemoveDocumentRequest = decodeGetDocumentRequest;
|
|
223
|
+
|
|
224
|
+
function oneDocument<T>(value: unknown, decode: (input: unknown) => T): { document: T } {
|
|
225
|
+
const input = object(value); exact(input, ['document'], ['document']);
|
|
226
|
+
return { document: decode(input.document) };
|
|
227
|
+
}
|
|
228
|
+
export const decodePutDocumentResult = (value: unknown): PutDocumentResult => {
|
|
229
|
+
const result = oneDocument(value, decodeCubeDocument);
|
|
230
|
+
if (result.document.state !== 'active' || result.document.removed_at !== null || result.document.removed_by !== null) {
|
|
231
|
+
throw new ProtocolContractError('New document result must be active.');
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
};
|
|
235
|
+
export const decodeGetDocumentResult = (value: unknown): GetDocumentResult => oneDocument(value, decodeCubeDocument);
|
|
236
|
+
export const decodeRemoveDocumentResult = (value: unknown): RemoveDocumentResult => {
|
|
237
|
+
const result = oneDocument(value, decodeCubeDocumentMetadata);
|
|
238
|
+
if (result.document.state !== 'removed') throw new ProtocolContractError('Removed document result must be removed.');
|
|
239
|
+
return result;
|
|
240
|
+
};
|
|
241
|
+
export function decodeListDocumentsResult(value: unknown): ListDocumentsResult {
|
|
242
|
+
const input = object(value); exact(input, ['documents'], ['documents']);
|
|
243
|
+
if (!Array.isArray(input.documents) || input.documents.length > 500) throw new ProtocolContractError('Invalid document list.');
|
|
244
|
+
const documents = input.documents.map(decodeCubeDocumentMetadata);
|
|
245
|
+
if (documents.some(({ state }) => state === 'removed')) throw new ProtocolContractError('Removed documents must be delisted.');
|
|
246
|
+
return { documents };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export const decodePutDocumentRequestEnvelope = (value: unknown): ProtocolEnvelope<PutDocumentRequest> => decodeProtocolEnvelope(value, decodePutDocumentRequest);
|
|
250
|
+
export const decodePutDocumentResultEnvelope = (value: unknown): ProtocolEnvelope<PutDocumentResult> => decodeProtocolEnvelope(value, decodePutDocumentResult);
|
|
251
|
+
export const decodeGetDocumentRequestEnvelope = (value: unknown): ProtocolEnvelope<GetDocumentRequest> => decodeProtocolEnvelope(value, decodeGetDocumentRequest);
|
|
252
|
+
export const decodeGetDocumentResultEnvelope = (value: unknown): ProtocolEnvelope<GetDocumentResult> => decodeProtocolEnvelope(value, decodeGetDocumentResult);
|
|
253
|
+
export const decodeListDocumentsRequestEnvelope = (value: unknown): ProtocolEnvelope<ListDocumentsRequest> => decodeProtocolEnvelope(value, decodeListDocumentsRequest);
|
|
254
|
+
export const decodeListDocumentsResultEnvelope = (value: unknown): ProtocolEnvelope<ListDocumentsResult> => decodeProtocolEnvelope(value, decodeListDocumentsResult);
|
|
255
|
+
export const decodeRemoveDocumentRequestEnvelope = (value: unknown): ProtocolEnvelope<RemoveDocumentRequest> => decodeProtocolEnvelope(value, decodeRemoveDocumentRequest);
|
|
256
|
+
export const decodeRemoveDocumentResultEnvelope = (value: unknown): ProtocolEnvelope<RemoveDocumentResult> => decodeProtocolEnvelope(value, decodeRemoveDocumentResult);
|
package/src/protocol/errors.ts
CHANGED
|
@@ -22,6 +22,11 @@ export enum ErrorCode {
|
|
|
22
22
|
ROLE_NOT_FOUND = 'ROLE_NOT_FOUND',
|
|
23
23
|
ROLE_SECTION_NOT_FOUND = 'ROLE_SECTION_NOT_FOUND',
|
|
24
24
|
ROLE_HAS_FROZEN_DRONES = 'ROLE_HAS_FROZEN_DRONES',
|
|
25
|
+
DOCUMENT_NOT_FOUND = 'DOCUMENT_NOT_FOUND',
|
|
26
|
+
DOCUMENT_CONTENT_TYPE_UNSUPPORTED = 'DOCUMENT_CONTENT_TYPE_UNSUPPORTED',
|
|
27
|
+
DOCUMENT_BUDGET_EXCEEDED = 'DOCUMENT_BUDGET_EXCEEDED',
|
|
28
|
+
DOCUMENT_SUPERSESSION_INVALID = 'DOCUMENT_SUPERSESSION_INVALID',
|
|
29
|
+
DOCUMENT_REMOVE_DENIED = 'DOCUMENT_REMOVE_DENIED',
|
|
25
30
|
CUBE_DELETED = 'CUBE_DELETED',
|
|
26
31
|
DRONE_EVICTED = 'DRONE_EVICTED',
|
|
27
32
|
DRONE_FROZEN = 'DRONE_FROZEN',
|
package/src/protocol/index.ts
CHANGED
package/src/protocol/sse.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EnrichedStreamEntry } from './types.js';
|
|
2
|
+
import { decodeDocumentCitations } from './documents.js';
|
|
2
3
|
import {
|
|
3
4
|
ProtocolContractError,
|
|
4
5
|
decodeCanonicalTimestamp,
|
|
@@ -6,15 +7,41 @@ import {
|
|
|
6
7
|
decodeLogCursor,
|
|
7
8
|
decodeOpaqueIdentifier,
|
|
8
9
|
decodeUuid,
|
|
10
|
+
PROTOCOL_LIMIT_CEILINGS,
|
|
9
11
|
utf8ByteLength,
|
|
10
12
|
type LogCursor,
|
|
11
13
|
type ProtocolErrorEnvelope,
|
|
12
14
|
} from './contract.js';
|
|
13
15
|
|
|
16
|
+
const MAX_UUID = '00000000-0000-4000-8000-000000000000';
|
|
17
|
+
const MAX_TIMESTAMP = '0000-00-00T00:00:00.000Z';
|
|
18
|
+
const MAX_LOG_DATA_BYTES = utf8ByteLength(JSON.stringify({
|
|
19
|
+
cursor: { created_at: MAX_TIMESTAMP, id: MAX_UUID },
|
|
20
|
+
entry: {
|
|
21
|
+
id: MAX_UUID,
|
|
22
|
+
cube_id: MAX_UUID,
|
|
23
|
+
drone_id: MAX_UUID,
|
|
24
|
+
message: '\0'.repeat(PROTOCOL_LIMIT_CEILINGS.max_log_message_bytes),
|
|
25
|
+
visibility: 'broadcast',
|
|
26
|
+
created_at: MAX_TIMESTAMP,
|
|
27
|
+
drone_label: '\0'.repeat(120),
|
|
28
|
+
role_name: '\0'.repeat(120),
|
|
29
|
+
recipient_drone_ids: Array.from({ length: 100 }, () => MAX_UUID),
|
|
30
|
+
documents: Array.from({ length: 100 }, (_, index) => ({
|
|
31
|
+
id: `${index.toString().padStart(3, '0')}${'x'.repeat(125)}`,
|
|
32
|
+
title: '😀'.repeat(120),
|
|
33
|
+
size_bytes: 10 * 1024 * 1024,
|
|
34
|
+
state: 'superseded',
|
|
35
|
+
})),
|
|
36
|
+
},
|
|
37
|
+
}));
|
|
38
|
+
const MAX_LOG_FRAME_BYTES = MAX_LOG_DATA_BYTES +
|
|
39
|
+
utf8ByteLength(`event: log\nid: ${MAX_UUID}\ndata: `);
|
|
40
|
+
|
|
14
41
|
export const SSE_LIMITS = {
|
|
15
42
|
total_bytes: 1024 * 1024,
|
|
16
|
-
frame_bytes:
|
|
17
|
-
data_bytes:
|
|
43
|
+
frame_bytes: MAX_LOG_FRAME_BYTES,
|
|
44
|
+
data_bytes: MAX_LOG_DATA_BYTES,
|
|
18
45
|
frame_count: 1000,
|
|
19
46
|
unknown_data_bytes: 4096,
|
|
20
47
|
} as const;
|
|
@@ -97,6 +124,7 @@ export function decodeEnrichedStreamEntry(value: unknown): EnrichedStreamEntry {
|
|
|
97
124
|
'drone_label',
|
|
98
125
|
'role_name',
|
|
99
126
|
'recipient_drone_ids',
|
|
127
|
+
'documents',
|
|
100
128
|
],
|
|
101
129
|
[
|
|
102
130
|
'id',
|
|
@@ -120,7 +148,11 @@ export function decodeEnrichedStreamEntry(value: unknown): EnrichedStreamEntry {
|
|
|
120
148
|
id: decodeUuid(entry.id, ['entry', 'id']),
|
|
121
149
|
cube_id: decodeUuid(entry.cube_id, ['entry', 'cube_id']),
|
|
122
150
|
drone_id: entry.drone_id === null ? null : decodeUuid(entry.drone_id, ['entry', 'drone_id']),
|
|
123
|
-
message: boundedString(
|
|
151
|
+
message: boundedString(
|
|
152
|
+
entry.message,
|
|
153
|
+
'message',
|
|
154
|
+
PROTOCOL_LIMIT_CEILINGS.max_log_message_bytes,
|
|
155
|
+
),
|
|
124
156
|
visibility: entry.visibility,
|
|
125
157
|
created_at: decodeCanonicalTimestamp(entry.created_at, ['entry', 'created_at']),
|
|
126
158
|
drone_label: nullableString(entry.drone_label, 'drone_label', 120),
|
|
@@ -128,6 +160,9 @@ export function decodeEnrichedStreamEntry(value: unknown): EnrichedStreamEntry {
|
|
|
128
160
|
recipient_drone_ids: entry.recipient_drone_ids.map((id, index) =>
|
|
129
161
|
decodeUuid(id, ['entry', 'recipient_drone_ids', index])
|
|
130
162
|
),
|
|
163
|
+
...(entry.documents === undefined ? {} : {
|
|
164
|
+
documents: decodeDocumentCitations(entry.documents),
|
|
165
|
+
}),
|
|
131
166
|
};
|
|
132
167
|
}
|
|
133
168
|
|
package/src/protocol/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MessageTaxonomy } from '../templates.js';
|
|
2
|
+
import type { DocumentCitation } from './documents.js';
|
|
2
3
|
|
|
3
4
|
export type AgentKind = 'claude' | 'codex' | 'opencode';
|
|
4
5
|
export type RoleClass = 'queen' | 'worker';
|
|
@@ -100,6 +101,7 @@ export interface ActivityLogEntry {
|
|
|
100
101
|
message: string;
|
|
101
102
|
visibility: LogVisibility;
|
|
102
103
|
created_at: string;
|
|
104
|
+
documents?: DocumentCitation[];
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
export interface EnrichedStreamEntry extends ActivityLogEntry {
|
|
@@ -177,6 +179,7 @@ export interface AppendLogRequest {
|
|
|
177
179
|
recipientDroneIds?: string[];
|
|
178
180
|
class?: string;
|
|
179
181
|
to?: string[];
|
|
182
|
+
documents?: string[];
|
|
180
183
|
}
|
|
181
184
|
|
|
182
185
|
export interface AppendLogResponse {
|
|
@@ -184,6 +187,10 @@ export interface AppendLogResponse {
|
|
|
184
187
|
deduplicated: boolean;
|
|
185
188
|
routing?: RoutingEcho | null;
|
|
186
189
|
unreachableRecipients?: Array<{ id: string; label: string }>;
|
|
190
|
+
advisory?: {
|
|
191
|
+
code: 'STORE_AS_DOCUMENT';
|
|
192
|
+
threshold_bytes: number;
|
|
193
|
+
};
|
|
187
194
|
}
|
|
188
195
|
|
|
189
196
|
export interface Decision {
|
package/src/protocol/version.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Current Borg coordination protocol generation. Clean-slate
|
|
2
|
-
export const PROTOCOL_VERSION = '
|
|
1
|
+
/** Current Borg coordination protocol generation. Clean-slate v10. */
|
|
2
|
+
export const PROTOCOL_VERSION = '10' as const;
|
|
3
3
|
|
|
4
4
|
export type ProtocolVersion = typeof PROTOCOL_VERSION;
|