@holokai/harbor-sdk 0.3.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 +72 -0
- package/dist/chunks/chunk-G4KJ2OCU.js +319 -0
- package/dist/contract.d.ts +613 -0
- package/dist/contract.js +51 -0
- package/dist/holokai-logo.d.ts +5 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.js +212 -0
- package/dist/manifest.d.ts +26 -0
- package/dist/manifest.js +1 -0
- package/dist/package.d.ts +79 -0
- package/dist/package.js +131 -0
- package/dist/page-reader.d.ts +53 -0
- package/dist/runtime.d.ts +220 -0
- package/dist/runtime.js +56 -0
- package/dist/sign-in-page.d.ts +5 -0
- package/dist/sign-in-page.js +96 -0
- package/package.json +58 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE PUBLIC WIRE of harbor's extension to MCP Apps — spelled once, here, with
|
|
3
|
+
* ZERO imports. Harbor's gate imports this module for runtime validation and
|
|
4
|
+
* the SDK client imports it for its types, so the two cannot drift.
|
|
5
|
+
*
|
|
6
|
+
* Everything here is a PROJECTION of harbor, never harbor's internals: no
|
|
7
|
+
* `RunView`, no `FusionEventMap`, no IPC `*View` — each public shape names
|
|
8
|
+
* what it keeps, and a field a hosted app needs that is not here becomes a
|
|
9
|
+
* new public field, never an import (ADR-HARBOR-012, decision 5).
|
|
10
|
+
*
|
|
11
|
+
* Versioning: `HARBOR_EXTENSION_VERSION` rides the handshake; methods and
|
|
12
|
+
* events are additive within a major.
|
|
13
|
+
*/
|
|
14
|
+
/** The key under `hostCapabilities.experimental` (and under a tool's `_meta`). */
|
|
15
|
+
export declare const HARBOR_EXTENSION_ID = "ai.holokai/harbor";
|
|
16
|
+
/** The page reader's tool name, and the `reads` mark under `_meta[HARBOR_EXTENSION_ID]` that names it. */
|
|
17
|
+
export declare const HARBOR_PAGE_READER: {
|
|
18
|
+
readonly tool: "read_page";
|
|
19
|
+
readonly reads: "page";
|
|
20
|
+
};
|
|
21
|
+
/** The extension's own version — independent of the MCP Apps protocol version. */
|
|
22
|
+
export declare const HARBOR_EXTENSION_VERSION = "0.2";
|
|
23
|
+
/** The MCP Apps protocol revision harbor speaks. */
|
|
24
|
+
export declare const MCP_APPS_PROTOCOL_VERSION = "2026-01-26";
|
|
25
|
+
export declare const HARBOR_METHODS: {
|
|
26
|
+
readonly workflowsList: "harbor/workflows/list";
|
|
27
|
+
readonly workflowsSubmit: "harbor/workflows/submit";
|
|
28
|
+
readonly workflowsGet: "harbor/workflows/get";
|
|
29
|
+
readonly capabilitiesList: "harbor/capabilities/list";
|
|
30
|
+
readonly eventsSubscribe: "harbor/events/subscribe";
|
|
31
|
+
readonly eventsUnsubscribe: "harbor/events/unsubscribe";
|
|
32
|
+
readonly filesOpen: "harbor/files/open";
|
|
33
|
+
readonly filesAdd: "harbor/files/add";
|
|
34
|
+
readonly filesLink: "harbor/files/link";
|
|
35
|
+
readonly browserOpen: "harbor/browser/open";
|
|
36
|
+
readonly barSet: "harbor/bar/set";
|
|
37
|
+
readonly viewsOpen: "harbor/views/open";
|
|
38
|
+
readonly viewStatus: "harbor/view/status";
|
|
39
|
+
};
|
|
40
|
+
export type HarborMethod = (typeof HARBOR_METHODS)[keyof typeof HARBOR_METHODS];
|
|
41
|
+
/** The host → view notification that carries subscribed events. */
|
|
42
|
+
export declare const HARBOR_EVENT_NOTIFICATION = "harbor/notifications/event";
|
|
43
|
+
/** The host → view notification that carries what the person typed or chose in the window bar. */
|
|
44
|
+
export declare const HARBOR_BAR_NOTIFICATION = "harbor/notifications/bar";
|
|
45
|
+
/**
|
|
46
|
+
* A run's public status. The contract's OWN union, mapped exhaustively from
|
|
47
|
+
* harbor's internal vocabulary with `unknown` as the registered fallback.
|
|
48
|
+
*/
|
|
49
|
+
export type WorkflowStatus = 'running' | 'held' | 'done' | 'done_with_errors' | 'failed' | 'cancelled' | 'unknown';
|
|
50
|
+
/** What harbor says about one run. `title` is CONTENT (the user's goal text) and rides its own grant. */
|
|
51
|
+
export interface WorkflowSummary {
|
|
52
|
+
id: string;
|
|
53
|
+
chatId: string;
|
|
54
|
+
/** Empty when the `workflows.content` grant is absent — never a placeholder. */
|
|
55
|
+
title: string;
|
|
56
|
+
status: WorkflowStatus;
|
|
57
|
+
stepCount: number;
|
|
58
|
+
startedAtMs: number | null;
|
|
59
|
+
endedAtMs: number | null;
|
|
60
|
+
}
|
|
61
|
+
export interface WorkflowsListParams {
|
|
62
|
+
/** 1..50, default 20. Newest first. */
|
|
63
|
+
limit?: number;
|
|
64
|
+
}
|
|
65
|
+
export interface WorkflowsListResult {
|
|
66
|
+
workflows: WorkflowSummary[];
|
|
67
|
+
}
|
|
68
|
+
export declare const WORKFLOWS_LIST_LIMIT: {
|
|
69
|
+
readonly min: 1;
|
|
70
|
+
readonly max: 50;
|
|
71
|
+
readonly default: 20;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* One capability a DAG may name: the same four fields harbor pins for its
|
|
75
|
+
* own planner, plus whether it is the calling app's OWN tool
|
|
76
|
+
* (`mcp/<this instance>/<tool>`). Harbor's built-ins (`desktop/*`), Holo
|
|
77
|
+
* applications (`holo/<slug>`) and other connectors' tools all appear here;
|
|
78
|
+
* nothing that is disabled at the global scope does.
|
|
79
|
+
*/
|
|
80
|
+
export interface CapabilityEntry {
|
|
81
|
+
type: string;
|
|
82
|
+
description?: string;
|
|
83
|
+
inputSchema?: Record<string, unknown>;
|
|
84
|
+
outputSchema?: Record<string, unknown>;
|
|
85
|
+
own: boolean;
|
|
86
|
+
}
|
|
87
|
+
export interface CapabilitiesListResult {
|
|
88
|
+
capabilities: CapabilityEntry[];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* One task of a submitted DAG. `capability` must be a type from
|
|
92
|
+
* `harbor/capabilities/list`. `inputMappings` binds a dot-path INTO `input`
|
|
93
|
+
* (`messages.0.content`) to a dot-path into an upstream task's result
|
|
94
|
+
* (`results.<taskId>.data.text`) — the same convention harbor's own planner
|
|
95
|
+
* emits; the mapped slot in `input` is a `null` placeholder that the
|
|
96
|
+
* dispatcher overwrites. A mapping implies a dependency; `dependsOn` adds
|
|
97
|
+
* ordering-only edges. Every task's output is the ONE capability envelope
|
|
98
|
+
* (`{ok, version, data | error}`), so result paths start at `results.<id>.data`.
|
|
99
|
+
*/
|
|
100
|
+
export interface WorkflowTaskSpec {
|
|
101
|
+
id: string;
|
|
102
|
+
capability: string;
|
|
103
|
+
description?: string;
|
|
104
|
+
input?: Record<string, unknown>;
|
|
105
|
+
dependsOn?: string[];
|
|
106
|
+
inputMappings?: Record<string, string>;
|
|
107
|
+
}
|
|
108
|
+
export interface WorkflowsSubmitParams {
|
|
109
|
+
name: string;
|
|
110
|
+
description?: string;
|
|
111
|
+
tasks: WorkflowTaskSpec[];
|
|
112
|
+
}
|
|
113
|
+
export interface WorkflowsSubmitResult {
|
|
114
|
+
workflowId: string;
|
|
115
|
+
/** Task ids dispatched by the start — the DAG's roots. */
|
|
116
|
+
dispatched: string[];
|
|
117
|
+
}
|
|
118
|
+
export declare const WORKFLOW_TASKS_MAX = 50;
|
|
119
|
+
/** A task's public state. Mapped exhaustively from harbor's internal vocabulary. */
|
|
120
|
+
export type WorkflowTaskState = 'pending' | 'running' | 'done' | 'failed' | 'skipped' | 'cancelled' | 'unknown';
|
|
121
|
+
export interface WorkflowTaskView {
|
|
122
|
+
id: string;
|
|
123
|
+
capability: string;
|
|
124
|
+
state: WorkflowTaskState;
|
|
125
|
+
/** The task's result envelope once it has one, else null. CONTENT: an app sees only its own workflows'. */
|
|
126
|
+
output: unknown;
|
|
127
|
+
error: {
|
|
128
|
+
code: string | null;
|
|
129
|
+
message: string | null;
|
|
130
|
+
} | null;
|
|
131
|
+
}
|
|
132
|
+
export interface WorkflowsGetParams {
|
|
133
|
+
workflowId: string;
|
|
134
|
+
}
|
|
135
|
+
export interface WorkflowsGetResult {
|
|
136
|
+
workflowId: string;
|
|
137
|
+
name: string;
|
|
138
|
+
status: WorkflowStatus;
|
|
139
|
+
tasks: WorkflowTaskView[];
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Ask harbor to open a file this app identifies. Harbor calls `tool` on the
|
|
143
|
+
* app's OWN server with `arguments`; the tool answers a `CallToolResult` whose
|
|
144
|
+
* content holds exactly one `{type: 'resource', resource: {uri, mimeType?, blob}}`
|
|
145
|
+
* with the file's bytes base64 in `blob`. Harbor keeps its own read-only copy
|
|
146
|
+
* named `name` (a bare file name, no folders) and opens it in its editor.
|
|
147
|
+
*/
|
|
148
|
+
export interface FilesOpenParams {
|
|
149
|
+
name: string;
|
|
150
|
+
tool: string;
|
|
151
|
+
arguments?: Record<string, unknown>;
|
|
152
|
+
}
|
|
153
|
+
export interface FilesOpenResult {
|
|
154
|
+
/** The file name harbor opened the copy under. */
|
|
155
|
+
name: string;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Ask harbor to add a file this app identifies to the workspace the person
|
|
159
|
+
* opened this page to pick for. The same call as `harbor/files/open`: harbor
|
|
160
|
+
* calls `tool` on the app's OWN server with `arguments` for the bytes. The
|
|
161
|
+
* page never names the workspace; harbor decided it when it opened the page.
|
|
162
|
+
* Offered only to a page opened to pick files.
|
|
163
|
+
*/
|
|
164
|
+
export type FilesAddParams = FilesOpenParams;
|
|
165
|
+
export interface FilesAddResult {
|
|
166
|
+
/** The name it was added under: a file's in the workspace, which may differ from the one asked for, or a linked folder's. */
|
|
167
|
+
name: string;
|
|
168
|
+
/** True when the workspace already had it, so nothing changed. */
|
|
169
|
+
alreadyAdded?: boolean;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Ask harbor to link a folder or a single file this app identifies to the
|
|
173
|
+
* workspace the person opened this page to pick for: the workspace's chats then
|
|
174
|
+
* read it, read-only, through this app's own tools, and nothing is copied.
|
|
175
|
+
* Harbor asks the app's `get_session` for the account signed in and its
|
|
176
|
+
* `item_parents` for the item, and keeps only the app, the item's id, whether
|
|
177
|
+
* it is a folder or a file, and that account. Offered only to a first-party
|
|
178
|
+
* file app's page opened to pick files.
|
|
179
|
+
*/
|
|
180
|
+
export interface FilesLinkParams {
|
|
181
|
+
/** A folder or file id this app's own tools gave. */
|
|
182
|
+
id: string;
|
|
183
|
+
/** The folder's address on its provider's own site, kept only when the folder is picked as a workspace's file location. */
|
|
184
|
+
url?: string;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Ask harbor to open a URL in the person's own browser. A view's frame is
|
|
188
|
+
* sandboxed without `allow-popups`, so a link of its own goes nowhere; this
|
|
189
|
+
* is the one way out of the frame, and the URL leaves harbor for the
|
|
190
|
+
* operating system.
|
|
191
|
+
*
|
|
192
|
+
* HTTPS ONLY, and harbor decides that on the PARSED url, not this type: a
|
|
193
|
+
* page's own check is a convenience. Anything else — `http:`, `file:`,
|
|
194
|
+
* `javascript:`, an application's own scheme, an address that does not parse
|
|
195
|
+
* — is refused with `-32602`.
|
|
196
|
+
*/
|
|
197
|
+
export interface BrowserOpenParams {
|
|
198
|
+
url: string;
|
|
199
|
+
}
|
|
200
|
+
export interface BrowserOpenResult {
|
|
201
|
+
/** The URL harbor handed over, as it parsed it. */
|
|
202
|
+
url: string;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* A page hands harbor its search field and account menu, and harbor draws
|
|
206
|
+
* them in the window bar. Plain values only: harbor never runs page code or
|
|
207
|
+
* loads page markup there. Each call replaces the whole bar, and an absent or
|
|
208
|
+
* null field leaves that slot empty.
|
|
209
|
+
*/
|
|
210
|
+
export interface BarSearch {
|
|
211
|
+
placeholder: string;
|
|
212
|
+
/** The field's accessible label. */
|
|
213
|
+
label: string;
|
|
214
|
+
/** The current text: the person's own words. */
|
|
215
|
+
value: string;
|
|
216
|
+
}
|
|
217
|
+
export interface BarMenuItem {
|
|
218
|
+
id: string;
|
|
219
|
+
label: string;
|
|
220
|
+
}
|
|
221
|
+
export interface BarAccount {
|
|
222
|
+
/** Who is signed in, shown at the top of the menu. */
|
|
223
|
+
name: string;
|
|
224
|
+
items: BarMenuItem[];
|
|
225
|
+
}
|
|
226
|
+
export interface BarSetParams {
|
|
227
|
+
search?: BarSearch | null;
|
|
228
|
+
account?: BarAccount | null;
|
|
229
|
+
}
|
|
230
|
+
/** An empty object. */
|
|
231
|
+
export type BarSetResult = Record<string, never>;
|
|
232
|
+
/**
|
|
233
|
+
* The `harbor/notifications/bar` params: text typed in the bar's search, the
|
|
234
|
+
* id of a chosen menu item, or the id of a trail step the person pressed in
|
|
235
|
+
* harbor's window bar. A crumb's id is one the page reported in its view
|
|
236
|
+
* names' `trail`, and the page alone knows what going there means.
|
|
237
|
+
*/
|
|
238
|
+
export type BarInput = {
|
|
239
|
+
kind: 'search';
|
|
240
|
+
value: string;
|
|
241
|
+
} | {
|
|
242
|
+
kind: 'action';
|
|
243
|
+
id: string;
|
|
244
|
+
} | {
|
|
245
|
+
kind: 'crumb';
|
|
246
|
+
id: string;
|
|
247
|
+
};
|
|
248
|
+
/** The caps on page-supplied bar words, in characters, and on the menu's length. Harbor's gate is the one place they are enforced. */
|
|
249
|
+
export declare const BAR_LIMITS: {
|
|
250
|
+
readonly placeholderChars: 60;
|
|
251
|
+
readonly labelChars: 60;
|
|
252
|
+
readonly valueChars: 500;
|
|
253
|
+
readonly nameChars: 80;
|
|
254
|
+
readonly items: 8;
|
|
255
|
+
readonly itemLabelChars: 60;
|
|
256
|
+
readonly itemIdChars: 40;
|
|
257
|
+
};
|
|
258
|
+
/**
|
|
259
|
+
* What a page's tray button shows about the work behind the page: harbor's
|
|
260
|
+
* own tray vocabulary, chosen by the page. `running` draws the loader in
|
|
261
|
+
* place of the extension's icon, `waiting` a dot that says the work needs
|
|
262
|
+
* the person, `completed` and `failed` a mark on the corner, and `idle` the
|
|
263
|
+
* icon alone. The button is the extension's, so the status is shared by every
|
|
264
|
+
* view of it, and the last report wins. Harbor puts it back to `idle` when
|
|
265
|
+
* the extension's last view closes.
|
|
266
|
+
*/
|
|
267
|
+
export declare const VIEW_STATUSES: readonly ["idle", "running", "waiting", "completed", "failed"];
|
|
268
|
+
export type ViewStatus = (typeof VIEW_STATUSES)[number];
|
|
269
|
+
export interface ViewStatusParams {
|
|
270
|
+
status: ViewStatus;
|
|
271
|
+
}
|
|
272
|
+
/** An empty object. */
|
|
273
|
+
export type ViewStatusResult = Record<string, never>;
|
|
274
|
+
/**
|
|
275
|
+
* Ask harbor to open THIS SAME page — the same app, the same server, the same
|
|
276
|
+
* `ui://` document — in a new window beside the one asking, booted with
|
|
277
|
+
* `view` as its saved view. That is the whole reach of the method: the params
|
|
278
|
+
* carry no app, no tool and no address, so a page can open only itself.
|
|
279
|
+
*
|
|
280
|
+
* `view` is a {@link HarborViewState}, held to {@link validateViewState}: the
|
|
281
|
+
* new page reads it back through `savedView` exactly as it would a view it
|
|
282
|
+
* reported itself, so it shows the item `view.item` names. `title` is what the
|
|
283
|
+
* new window is called until its page reports names of its own: a noun, held to
|
|
284
|
+
* {@link HARBOR_VIEW_NAME_LIMITS} like the names a page reports, and kept in
|
|
285
|
+
* memory only.
|
|
286
|
+
*
|
|
287
|
+
* The new window is the person's like any other: resized, arranged and closed
|
|
288
|
+
* by harbor, never by the page.
|
|
289
|
+
*/
|
|
290
|
+
export interface ViewsOpenParams {
|
|
291
|
+
title: string;
|
|
292
|
+
view: HarborViewState;
|
|
293
|
+
}
|
|
294
|
+
/** An empty object: the new window is harbor's, and the page is told nothing about it. */
|
|
295
|
+
export type ViewsOpenResult = Record<string, never>;
|
|
296
|
+
/**
|
|
297
|
+
* The five facts a page reports about its own view, spelled ONCE for every
|
|
298
|
+
* page that has a view. Mail and the file browser say the same things under
|
|
299
|
+
* different nouns — a place and an item, how the list is narrowed, what was
|
|
300
|
+
* typed, how wide the panes are — and naming them twice is how the two drift.
|
|
301
|
+
*
|
|
302
|
+
* place a mail place id · a folder id
|
|
303
|
+
* item the open message id · the selected entry id
|
|
304
|
+
* filter Unread / Flagged · All / Folders / Documents
|
|
305
|
+
* query the search box text · the search box text
|
|
306
|
+
* panes list + places widths · details width
|
|
307
|
+
*
|
|
308
|
+
* Every field is optional: a page reports what it has. `panes` is the one
|
|
309
|
+
* bag, because harbor cannot know what a page calls its panes; its keys are
|
|
310
|
+
* validated by pattern and its values are finite numbers.
|
|
311
|
+
*
|
|
312
|
+
* This is the WHOLE view, never a patch — the slot it rides is
|
|
313
|
+
* last-write-wins, so an omitted field is an erased field.
|
|
314
|
+
*/
|
|
315
|
+
export interface HarborViewState {
|
|
316
|
+
place?: string;
|
|
317
|
+
item?: string;
|
|
318
|
+
filter?: string;
|
|
319
|
+
query?: string;
|
|
320
|
+
panes?: Record<string, number>;
|
|
321
|
+
}
|
|
322
|
+
/** The caps a reported view is held to. Strings are capped; the bag is bounded both ways. */
|
|
323
|
+
export declare const HARBOR_VIEW_STATE_LIMITS: {
|
|
324
|
+
/** Longest a `place`, `item`, `filter` or `query` may be. */
|
|
325
|
+
readonly valueChars: 256;
|
|
326
|
+
/** Most entries `panes` may carry. */
|
|
327
|
+
readonly paneEntries: 8;
|
|
328
|
+
/** Longest a pane name may be. */
|
|
329
|
+
readonly paneNameChars: 32;
|
|
330
|
+
};
|
|
331
|
+
/** The view state's capped string fields, in one list: the validator's loop and the size a saved view is held to. */
|
|
332
|
+
export declare const HARBOR_VIEW_STATE_STRING_KEYS: readonly ["place", "item", "filter", "query"];
|
|
333
|
+
export type HarborViewStateVerdict = {
|
|
334
|
+
ok: true;
|
|
335
|
+
value: HarborViewState;
|
|
336
|
+
} | {
|
|
337
|
+
ok: false;
|
|
338
|
+
message: string;
|
|
339
|
+
};
|
|
340
|
+
/**
|
|
341
|
+
* Grade one reported view: unknown top-level keys are refused, strings are
|
|
342
|
+
* capped, and the pane bag is checked key by key. The schema IS the allowlist,
|
|
343
|
+
* so nothing undeclared survives.
|
|
344
|
+
*/
|
|
345
|
+
export declare function validateViewState(value: unknown): HarborViewStateVerdict;
|
|
346
|
+
/**
|
|
347
|
+
* One step of the trail outside what a page has open: its name for the bar,
|
|
348
|
+
* and the page's own id for it. The id is opaque to harbor: it is handed back
|
|
349
|
+
* unchanged in a `crumb` bar input when the person presses the step, and the
|
|
350
|
+
* page resolves it against its own state.
|
|
351
|
+
*/
|
|
352
|
+
export interface HarborTrailStep {
|
|
353
|
+
id: string;
|
|
354
|
+
name: string;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* The nouns a page offers for a sentence about it: what it has open, where
|
|
358
|
+
* that sits, and the steps outside it. The person's own content, so they ride
|
|
359
|
+
* the CONTENT grant and the live rail — held in memory for as long as the view
|
|
360
|
+
* is mounted and written nowhere. The view state's `item` and `place` are the
|
|
361
|
+
* first two as ids, and those are what harbor remembers.
|
|
362
|
+
*
|
|
363
|
+
* Nouns, never clauses: the host spells every other word of the sentence
|
|
364
|
+
* these appear in.
|
|
365
|
+
*/
|
|
366
|
+
export interface HarborViewNames {
|
|
367
|
+
/** What the page has open — a message's subject, a file's name. */
|
|
368
|
+
item?: string;
|
|
369
|
+
/** Where that sits — a mail place, a folder. */
|
|
370
|
+
place?: string;
|
|
371
|
+
/**
|
|
372
|
+
* Every step outside `item`, outermost first, each with an id the page
|
|
373
|
+
* answers to. Harbor draws them as the pressable part of its trail; a
|
|
374
|
+
* page that gives none has `place` drawn as text. A first step named for
|
|
375
|
+
* the application is its root, and makes the application's own crumb
|
|
376
|
+
* pressable.
|
|
377
|
+
*/
|
|
378
|
+
trail?: HarborTrailStep[];
|
|
379
|
+
}
|
|
380
|
+
/** The key the nouns ride under `structuredContent`, beside the view state's own. */
|
|
381
|
+
export declare const HARBOR_VIEW_NAMES_ID = "ai.holokai/harbor/names";
|
|
382
|
+
/** The caps a noun is held to. Short: it goes in a sentence, not a document. */
|
|
383
|
+
export declare const HARBOR_VIEW_NAME_LIMITS: {
|
|
384
|
+
/** Longest an `item`, `place` or step name may be; a longer one is not kept. */
|
|
385
|
+
readonly nameChars: 120;
|
|
386
|
+
/** Most steps a trail may hold; past it, the first step and the innermost ones are kept. */
|
|
387
|
+
readonly trailSteps: 12;
|
|
388
|
+
/** Longest a step's id may be; a step with a longer one, or one holding a control character, is not kept. */
|
|
389
|
+
readonly stepIdChars: 256;
|
|
390
|
+
};
|
|
391
|
+
export type HarborViewNamesVerdict = {
|
|
392
|
+
ok: true;
|
|
393
|
+
value: HarborViewNames;
|
|
394
|
+
} | {
|
|
395
|
+
ok: false;
|
|
396
|
+
message: string;
|
|
397
|
+
};
|
|
398
|
+
/**
|
|
399
|
+
* Grade the nouns: unknown keys are refused and a wrong type is refused, but
|
|
400
|
+
* a name that is too long — or empty once its control characters are gone —
|
|
401
|
+
* is simply not kept, so one unusable noun never costs the report the other.
|
|
402
|
+
* A trail keeps its first steps up to the cap, and drops a step whose name or
|
|
403
|
+
* id is unusable; a trail with no usable step is not kept.
|
|
404
|
+
*/
|
|
405
|
+
export declare function validateViewNames(value: unknown): HarborViewNamesVerdict;
|
|
406
|
+
/**
|
|
407
|
+
* The params of `ui/update-model-context`, the spec's own method for telling
|
|
408
|
+
* the host what the view is showing. Harbor accepts it under the two `view.*`
|
|
409
|
+
* grants: the `structuredContent` half carries the view state under
|
|
410
|
+
* {@link HARBOR_EXTENSION_ID} and the nouns under {@link HARBOR_VIEW_NAMES_ID},
|
|
411
|
+
* the `content` half carries text for the model. Harbor advertises the
|
|
412
|
+
* modalities it takes, so a page feature-detects rather than probing.
|
|
413
|
+
*/
|
|
414
|
+
export declare const UPDATE_MODEL_CONTEXT_PARAMS_SCHEMA: JsonSchemaObject;
|
|
415
|
+
/** What harbor hands a booting page back under {@link HARBOR_EXTENSION_ID} in `hostContext`. */
|
|
416
|
+
export interface HarborHostContext {
|
|
417
|
+
/** The view harbor saved for this page, or null when it has none. */
|
|
418
|
+
viewState: HarborViewState | null;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Every public event, with the payload a view receives. Each is a projection
|
|
422
|
+
* of one harbor-internal event; the projection (which fields survive) lives
|
|
423
|
+
* in harbor, and the view only ever sees these shapes.
|
|
424
|
+
*/
|
|
425
|
+
export interface HarborEventMap {
|
|
426
|
+
/** Harbor's liveness tick — diagnostic, ungated. */
|
|
427
|
+
'harbor.heartbeat': {
|
|
428
|
+
count: number;
|
|
429
|
+
};
|
|
430
|
+
/** A run was created or changed. An INVALIDATION signal: re-read on a `seq` gap. */
|
|
431
|
+
'run.changed': {
|
|
432
|
+
workflow: WorkflowSummary;
|
|
433
|
+
};
|
|
434
|
+
/** A task of a workflow THIS app submitted moved. Progress is numbers only. */
|
|
435
|
+
'workflow.task': {
|
|
436
|
+
workflowId: string;
|
|
437
|
+
taskId: string;
|
|
438
|
+
state: WorkflowTaskState;
|
|
439
|
+
progress?: {
|
|
440
|
+
percent?: number;
|
|
441
|
+
outputTokens?: number;
|
|
442
|
+
reasoningTokens?: number;
|
|
443
|
+
};
|
|
444
|
+
};
|
|
445
|
+
/** A workflow this app submitted started or ended. On a terminal status, read it back. */
|
|
446
|
+
'workflow.changed': {
|
|
447
|
+
workflowId: string;
|
|
448
|
+
status: WorkflowStatus;
|
|
449
|
+
};
|
|
450
|
+
/**
|
|
451
|
+
* The app's own server told its open views something, named by the server. Delivered as sent,
|
|
452
|
+
* not a projection of anything harbor holds; on a `seq` gap the view re-reads from its server.
|
|
453
|
+
*/
|
|
454
|
+
'server.notification': {
|
|
455
|
+
name: string;
|
|
456
|
+
params: Record<string, unknown>;
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
export type HarborEventName = keyof HarborEventMap;
|
|
460
|
+
/** The closed set, as data, for runtime checks. */
|
|
461
|
+
export declare const HARBOR_EVENT_NAMES: readonly HarborEventName[];
|
|
462
|
+
export interface EventsSubscribeParams {
|
|
463
|
+
events: HarborEventName[];
|
|
464
|
+
}
|
|
465
|
+
export interface EventsSubscribeResult {
|
|
466
|
+
subscriptionId: string;
|
|
467
|
+
events: HarborEventName[];
|
|
468
|
+
}
|
|
469
|
+
export interface EventsUnsubscribeParams {
|
|
470
|
+
subscriptionId: string;
|
|
471
|
+
}
|
|
472
|
+
/** An empty object — JSON-RPC results are objects, never bare scalars. */
|
|
473
|
+
export type EventsUnsubscribeResult = Record<string, never>;
|
|
474
|
+
/**
|
|
475
|
+
* The `harbor/notifications/event` params. `seq` is PER SUBSCRIPTION, starts
|
|
476
|
+
* at 1 and increments by one per delivered event; a skipped number means an
|
|
477
|
+
* event was lost and the view re-reads its snapshot. There is no replay.
|
|
478
|
+
*/
|
|
479
|
+
export interface HarborEventNotificationParams<K extends HarborEventName = HarborEventName> {
|
|
480
|
+
subscriptionId: string;
|
|
481
|
+
seq: number;
|
|
482
|
+
event: K;
|
|
483
|
+
payload: HarborEventMap[K];
|
|
484
|
+
}
|
|
485
|
+
/** Method → its params and result, for typed clients and typed gates alike. */
|
|
486
|
+
export interface HarborMethodMap {
|
|
487
|
+
'harbor/workflows/list': {
|
|
488
|
+
params: WorkflowsListParams;
|
|
489
|
+
result: WorkflowsListResult;
|
|
490
|
+
};
|
|
491
|
+
'harbor/workflows/submit': {
|
|
492
|
+
params: WorkflowsSubmitParams;
|
|
493
|
+
result: WorkflowsSubmitResult;
|
|
494
|
+
};
|
|
495
|
+
'harbor/workflows/get': {
|
|
496
|
+
params: WorkflowsGetParams;
|
|
497
|
+
result: WorkflowsGetResult;
|
|
498
|
+
};
|
|
499
|
+
'harbor/capabilities/list': {
|
|
500
|
+
params: Record<string, never>;
|
|
501
|
+
result: CapabilitiesListResult;
|
|
502
|
+
};
|
|
503
|
+
'harbor/events/subscribe': {
|
|
504
|
+
params: EventsSubscribeParams;
|
|
505
|
+
result: EventsSubscribeResult;
|
|
506
|
+
};
|
|
507
|
+
'harbor/events/unsubscribe': {
|
|
508
|
+
params: EventsUnsubscribeParams;
|
|
509
|
+
result: EventsUnsubscribeResult;
|
|
510
|
+
};
|
|
511
|
+
'harbor/files/open': {
|
|
512
|
+
params: FilesOpenParams;
|
|
513
|
+
result: FilesOpenResult;
|
|
514
|
+
};
|
|
515
|
+
'harbor/files/add': {
|
|
516
|
+
params: FilesAddParams;
|
|
517
|
+
result: FilesAddResult;
|
|
518
|
+
};
|
|
519
|
+
'harbor/files/link': {
|
|
520
|
+
params: FilesLinkParams;
|
|
521
|
+
result: FilesAddResult;
|
|
522
|
+
};
|
|
523
|
+
'harbor/browser/open': {
|
|
524
|
+
params: BrowserOpenParams;
|
|
525
|
+
result: BrowserOpenResult;
|
|
526
|
+
};
|
|
527
|
+
'harbor/bar/set': {
|
|
528
|
+
params: BarSetParams;
|
|
529
|
+
result: BarSetResult;
|
|
530
|
+
};
|
|
531
|
+
'harbor/views/open': {
|
|
532
|
+
params: ViewsOpenParams;
|
|
533
|
+
result: ViewsOpenResult;
|
|
534
|
+
};
|
|
535
|
+
'harbor/view/status': {
|
|
536
|
+
params: ViewStatusParams;
|
|
537
|
+
result: ViewStatusResult;
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* The JSON Schema subset the wire is validated against: top-level `type`,
|
|
542
|
+
* `properties`, `required`, and per-property `type` / `enum`. Undeclared
|
|
543
|
+
* fields are refused — the schema IS the allowlist. Nested shapes are checked
|
|
544
|
+
* by the handler, which is why `events` names its closed set again in
|
|
545
|
+
* `HARBOR_EVENT_NAMES`, and why the bar's caps (`BAR_LIMITS`) are enforced by
|
|
546
|
+
* harbor's gate and nowhere here.
|
|
547
|
+
*/
|
|
548
|
+
export interface JsonSchemaObject {
|
|
549
|
+
type: 'object';
|
|
550
|
+
properties: Record<string, {
|
|
551
|
+
type: string;
|
|
552
|
+
enum?: readonly string[];
|
|
553
|
+
description?: string;
|
|
554
|
+
}>;
|
|
555
|
+
required?: readonly string[];
|
|
556
|
+
}
|
|
557
|
+
export declare const HARBOR_METHOD_PARAMS_SCHEMA: Record<HarborMethod, JsonSchemaObject>;
|
|
558
|
+
/**
|
|
559
|
+
* The closed grant vocabulary. Content (a run's title) and metadata (its
|
|
560
|
+
* status, timestamps) are DIFFERENT grants from day one, and a reported view
|
|
561
|
+
* splits the same way: `view.state` lets harbor remember the structured view
|
|
562
|
+
* and hand it back, `view.content` lets what a page says it is showing — its
|
|
563
|
+
* words and the names of what it has open — reach the window's report.
|
|
564
|
+
* `bar.set` lets a page hand its search field and account menu to the window
|
|
565
|
+
* bar, and receive what the person types or chooses there. `views.open` lets
|
|
566
|
+
* a page open itself again in a new window beside its own. `files.add` lets a
|
|
567
|
+
* page opened to pick files add one to the workspace it was opened for, or
|
|
568
|
+
* link a folder to it. `view.state` also admits `harbor/view/status`: the
|
|
569
|
+
* standing a page gives its own tray button is part of the view harbor keeps
|
|
570
|
+
* for it, and carries none of the page's words.
|
|
571
|
+
*/
|
|
572
|
+
export type HarborGrant = 'workflows.read' | 'workflows.content' | 'workflows.submit' | 'capabilities.read' | 'server.tools' | 'server.resources' | 'events.runs' | 'events.diagnostic' | 'files.open' | 'files.add' | 'browser.open' | 'view.state' | 'view.content' | 'bar.set' | 'views.open';
|
|
573
|
+
/**
|
|
574
|
+
* The grants an extension must declare in its manifest to be given: harbor
|
|
575
|
+
* gives one to no extension that does not name it, whatever else it is given.
|
|
576
|
+
*/
|
|
577
|
+
export type DeclaredGrant = Extract<HarborGrant, 'files.add'>;
|
|
578
|
+
/**
|
|
579
|
+
* Every method and every event, tied to its grant — exhaustive, so a new
|
|
580
|
+
* name cannot ship ungated. `by-params` is an explicit row, not an omission:
|
|
581
|
+
* the method itself is open, and the grant check runs against the names its
|
|
582
|
+
* params carry (a subscribe is gated per event).
|
|
583
|
+
*/
|
|
584
|
+
export declare const HARBOR_GRANT_OF: Record<HarborMethod | HarborEventName, HarborGrant | 'by-params'>;
|
|
585
|
+
/**
|
|
586
|
+
* The grants behind the STANDARD MCP Apps methods harbor answers, which are
|
|
587
|
+
* not `harbor/*` names and so live outside `HARBOR_GRANT_OF`'s key set.
|
|
588
|
+
* Spelled here so the vocabulary stays one table.
|
|
589
|
+
*
|
|
590
|
+
* A LIST means any one of them admits the method, and each half of its params
|
|
591
|
+
* is then gated on its own: withholding one grant leaves the other half
|
|
592
|
+
* working, and withholding both makes the method absent like any other
|
|
593
|
+
* ungranted one.
|
|
594
|
+
*/
|
|
595
|
+
export declare const STANDARD_METHOD_GRANT_OF: {
|
|
596
|
+
readonly 'tools/call': "server.tools";
|
|
597
|
+
readonly 'resources/list': "server.resources";
|
|
598
|
+
readonly 'resources/read': "server.resources";
|
|
599
|
+
readonly 'ui/update-model-context': readonly ["view.state", "view.content"];
|
|
600
|
+
};
|
|
601
|
+
/** The grant a field inside a result needs, beyond the method's own. */
|
|
602
|
+
export declare const WORKFLOW_TITLE_GRANT: HarborGrant;
|
|
603
|
+
/**
|
|
604
|
+
* What harbor advertises under `hostCapabilities.experimental[HARBOR_EXTENSION_ID]`
|
|
605
|
+
* in the `ui/initialize` result: ONLY what this view is granted. The list is
|
|
606
|
+
* therefore the permission disclosure too, and a client feature-detects per
|
|
607
|
+
* method. An ungranted method answers `-32601` like an absent one.
|
|
608
|
+
*/
|
|
609
|
+
export interface HarborHostExtension {
|
|
610
|
+
version: string;
|
|
611
|
+
methods: HarborMethod[];
|
|
612
|
+
events: HarborEventName[];
|
|
613
|
+
}
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {createRequire as __harborCreateRequire} from 'node:module'; const require = __harborCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
BAR_LIMITS,
|
|
4
|
+
HARBOR_BAR_NOTIFICATION,
|
|
5
|
+
HARBOR_EVENT_NAMES,
|
|
6
|
+
HARBOR_EVENT_NOTIFICATION,
|
|
7
|
+
HARBOR_EXTENSION_ID,
|
|
8
|
+
HARBOR_EXTENSION_VERSION,
|
|
9
|
+
HARBOR_GRANT_OF,
|
|
10
|
+
HARBOR_METHODS,
|
|
11
|
+
HARBOR_METHOD_PARAMS_SCHEMA,
|
|
12
|
+
HARBOR_PAGE_READER,
|
|
13
|
+
HARBOR_VIEW_NAMES_ID,
|
|
14
|
+
HARBOR_VIEW_NAME_LIMITS,
|
|
15
|
+
HARBOR_VIEW_STATE_LIMITS,
|
|
16
|
+
HARBOR_VIEW_STATE_STRING_KEYS,
|
|
17
|
+
MCP_APPS_PROTOCOL_VERSION,
|
|
18
|
+
STANDARD_METHOD_GRANT_OF,
|
|
19
|
+
UPDATE_MODEL_CONTEXT_PARAMS_SCHEMA,
|
|
20
|
+
VIEW_STATUSES,
|
|
21
|
+
WORKFLOWS_LIST_LIMIT,
|
|
22
|
+
WORKFLOW_TASKS_MAX,
|
|
23
|
+
WORKFLOW_TITLE_GRANT,
|
|
24
|
+
validateViewNames,
|
|
25
|
+
validateViewState
|
|
26
|
+
} from "./chunks/chunk-G4KJ2OCU.js";
|
|
27
|
+
export {
|
|
28
|
+
BAR_LIMITS,
|
|
29
|
+
HARBOR_BAR_NOTIFICATION,
|
|
30
|
+
HARBOR_EVENT_NAMES,
|
|
31
|
+
HARBOR_EVENT_NOTIFICATION,
|
|
32
|
+
HARBOR_EXTENSION_ID,
|
|
33
|
+
HARBOR_EXTENSION_VERSION,
|
|
34
|
+
HARBOR_GRANT_OF,
|
|
35
|
+
HARBOR_METHODS,
|
|
36
|
+
HARBOR_METHOD_PARAMS_SCHEMA,
|
|
37
|
+
HARBOR_PAGE_READER,
|
|
38
|
+
HARBOR_VIEW_NAMES_ID,
|
|
39
|
+
HARBOR_VIEW_NAME_LIMITS,
|
|
40
|
+
HARBOR_VIEW_STATE_LIMITS,
|
|
41
|
+
HARBOR_VIEW_STATE_STRING_KEYS,
|
|
42
|
+
MCP_APPS_PROTOCOL_VERSION,
|
|
43
|
+
STANDARD_METHOD_GRANT_OF,
|
|
44
|
+
UPDATE_MODEL_CONTEXT_PARAMS_SCHEMA,
|
|
45
|
+
VIEW_STATUSES,
|
|
46
|
+
WORKFLOWS_LIST_LIMIT,
|
|
47
|
+
WORKFLOW_TASKS_MAX,
|
|
48
|
+
WORKFLOW_TITLE_GRANT,
|
|
49
|
+
validateViewNames,
|
|
50
|
+
validateViewState
|
|
51
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Holokai logo, the holokai.ai website's PNG (566 by 566 pixels), as a data URI, so a page
|
|
3
|
+
* shows it with no file beside it and no request.
|
|
4
|
+
*/
|
|
5
|
+
export declare const HOLOKAI_LOGO_PNG = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAjYAAAI2CAMAAAB0aDIHAAAAM1BMVEUAAAAtha0tha0tha0tha0tha0tha0tha0tha0tha0tha0tha0tha0tha0tha0tha0tha2BV9aoAAAAEHRSTlMAgL9AEO9gnzDfIM+PcK9Q+JwXawAAEkVJREFUeNrs3W1OAjEUhtGLMHyJcve/WiWTSCIR5i3885xFPO1ty1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAPrY0Ho3L0vyOy63woyU3dvCxL7/nYoSJy65YbUoS+mTcFi656tCpI5Sm5ITS03pLbdckPqrVtuCB175uyGwKlnzm4ITC03DGyI5YaBDbHckN9iyg1DJ8RX3t2QTN9X64Jg+p7tCu7aTH3LDQP3rfqHGway91ltBic96rt1LghiM/sseBAbm2Keic3VR0Eam34vSGIz8yEB7sTGKkUeG6sUA7GxSpHHxirFQGysUuSxsUrxbGy8DSWKjXspXhWbngoexcaTYuLYeOPH4tgYwRmPjUc3DMXG01CWxcbmhtfExq96yWNzq+B3bJzcMBobjydIY+Naiidj40e9LIiNAz/S2NgTE9lcYmNPTGbVf/NdLR58PMs5MXlsnBPzTGz8fIFlsTFKMRwbbycIYpM5Ff/dHBujFGP/ruBWivDfFUzgDMTGBE4eGxM4L4iN7058sXcvuY3DUBBFKVKfSLHjt//VNgJk1JOklHhg+5w1XBAGS5L5wWHj4oZEv2c2x3X66J/e6z/90+bMelCjAunFzaW+8+45wYfU75jNVqWbpzQqEN73rX4kPateZ7VvbWaKJzXqjtl02TypvU47ZPOq1jpvyOZV7bIhtpZsSM17/cIqm9c01W9MsnlJ8yIbYlPJhvywkQ2xrWRD6ijZENtkQ+wo2RDrsiE2SjbEumyIjZINsV02xNaSDbFdNsSuJRvyDVM2xKaSDflhIxtil5INqaNkQ2yTDbG3kg2xLhtio2RDrMuG2K1kQ2yXDbG1ZENq3mVDbCrZkG+YsiF2KdmQOko2xDbZEHsr2RDrsiE2SjbE3mVDbC3ZENtlQ+xaJRvuNyssF9mQbpgfc/u7bCbZPLJjqZ86mmxIZ4WtyYZ4wzxkQzwrbE02xLPCIRtOHDayIZ4VDtkQzwpbkw3prLDMsiGeFaYmG9JZYZllQzwrTE02pLPCPsuGeFZYm2xIb/r2JhviWWHIhviw6U02xLPCkA0nZgXZEL+tcMiGE7OCbMhnBdkQzwprkw3xrNBkQ/wRpCEbTtz0yYb8bQXZEH8E6dJkQzorLLNsiGeFa5MN6U3f3mRDPCsM2RAfNr3JhvgB4jfZcOIjSLIhvelbZtkQzwpTkw3pYbM32RDf9N1kQzwr9CYb4r9sHrIhvunbmmyIZ4VDNsQ3fVOTDemssMyy4ctHcNjIhvymTzbEs8IqG77cgsNGNsSzwpAN8XdJepMN+QPEsvnH3r3kuA0DURQtkvrLVmr/qw2VZJL0QH4M0RKlexbQDRgXb+ASZBQ8QEw2kN9LksgG8lkhGtlA/xVVskHBDxuSDfT3kpAN/liFsSEb6L+iSjYoeN052UD9ps+nitkMZNMy4aywWsVsEtm0TBiblx0byOYJFv9cTIfeTjZPMHpdZPMEk3+KbFBwViAbFJwVyAb6WcHJBvpZwckG4llhRzb4LbiAbCCeFXZkg19eriAb7BaXkA0Kzgpkg4KzAtmg4KxANig4K5ANCs4KZAMbXEU2sNlVZIPgpyOb5nS9n45smnOBsSGb5ix+AWTTmtEvgGwac4mxIZvWRL8CsmnL6WcFsmnRNcaGbNpy/lmBbBp0/lmBbNpzlbEhm5Zc4axANs25wlmBbFpznbEhm4a8/DLIphmLS9Z3OhC+6MnmdkYXbJ39i1c+PtHkisW+4AWzTxRdEKxMTzb3klzQd6Vtks29iGNDNsgGcWwKjWRzK7M2NqUC2dyJODZkA/msMBjZQL1hzkY20MeGbFAwNmQD+YY5kA3kG+ZqZAN5bBLZYLe5IBrZIEv62JTbyOYmYsnYcMp8OHFsyAby2GxGNsgGVyxkA/mBifH//x3Z3EH52PAI+nN1vTY2ZIMsuGIhG2RL74JgZINsrPAoqKQjm/Yl/+6xSWTTvqiNDdkgG8SxIRuYdbM2NmSDLLhiMLKB+k3fbGSDbHTFQDaQHyCejWygjw3ZIEuuWI1skMWSR0E5Ljxc8pLnzjllPtzoikQ2yJazxsbWatm8yea7BRf00wkzN1VKPxiqmc+qxpa+2kjEzy5pqEarpqop+qH+1dmxbvQj22KoR6qmupTeIYtfbCH7kSbhLw1ht8W/hN07JaamrvAxPnkAAAAAAAAAAAAAAH6ydy/ajYJQFIbhoKCCet7/aWcmbSadNpmo8ULk/96gq3vBFqIHADBV4y7sF979we8G8VV3+fFhEBF9RkSs9SSoYM5dsqKLtJIs4SlI5UabJOrrgsH5Vc7bQXQ1dWNwZp2zSWr9g9Tgucr1SXQT3uCEutEOrV6RGjzTeCu1bioZnEjnn29KPEThphsfrTGkBne5tXoM30IpROND1Cdow7ip3IRtidTgpulDqweoz5qaoBrFemfOqhqt6GScDc8cGhUHO57tr5y3yJCaBd/pu4XnFFf8l0XmWPHEqTGj/iTJv/Of7Pqh1cPFc39pbNDfzpKdxqeoWTh5aj7njb1/drrjt6WCUvN8wEItufedj5uCnJw/NcYkfa4dbJYfZM0vMaWkZvqkgzb0GR3wuD5ltCuVlxrT1DpdPD47zWglg2elwlMza1L8bc/qzO4q57MOzEWddxFcU9AFakn95oXnlpchwxJT2Nnwd1XUxSTYcav0NM7bJJkcxXDn/UOjr4qSbL/Oy8/OjdYGybTw8rvhL3pdTSsyXN+hrya+V/0x1mWQ91pZGIdnBt1OLfe8fUZ+akt5iPqrOt3/cBLq8Iuat3hQyVpRdfjKK3i1Zb6k4HR4vrd85M1FicXmQ5X7wX3Oiiw21GKKzXKjgmLD49RDFBtz+GV48QouNuRmscGAWwauorid2kHpxYbcLNEbkBuKDcd+91BsPpGbGSg2f5GbGSg222m41qTY0IuvKDb/IjdTUGy+IzcTcBV1F/dT/8VvbO4hN3uKBvz+Zq6aOvyY4wCHX2Yt0FCMqcMLVIOCgT/z9Qoeorhp4IMkj7BR/cDrLfvreaIiNQt0vCHOgQ0LDgc2D7HgKKnJgC9+wWlJzQJV4d9Oog2zU5GaPblib6kCqXmFL/PUuLBPnBtDcBgGnwdf2FZ16gHNe3IlleNErVlNl2o9RC0iwd4EEdENtc5gRZUX3ZGItY/nxnTO2yAbJNmy1Kyus61urhU7eVRe5XxaMzxDOWMM99WkDZOzcLZi5WyI+jphf9pQY6OurX19cr3rX8tOYKXZWuVDqyuJ4TKv/tiR4dHTafbR+RRfX2Ias77G21mFJ/YsNPtyfRCdTwY7OrOpyk161IppZJ05Rud6KzLtDMYunQm9fKx4kHh/rUu9IzLH665Td/8VrLW9c84cqbmMBP40Ose2BAAAAAAAAAAAAAAAAAAAAAAA8Iu9c9uWGoQBKC3h3kv+/2t1qctlM4yQlOqI2W9ezjQHdmlDGFAURVEURVEURVEURVEURVEURVEURVEURVEURVEURVGUv0C0P/iIE0d/RKP7l/PJ9gKY32IvsPreLmuiZ7GQq3EvLQdOcmBLOhZrHogGLKEz8rxQDKH3ktk8wYIXGm2HF1bTCfgDq6zk/BzGpeXkvWCVw8PoaApS+uwMSDm7jcYL3337B7WxAX/DYf+sNueKvyGMjSYgxZsu3Gtk/5c2dsUGyf85bXzin24pj8ZLO//EFxz8R9rYFTso9s9oYxN2sMZB0URElIV94Cv+v9EGNuwkwPPa5AM7WYZEkx0SCpguACsc/4s2NmE3zj6tzemwmxLvRwMFCS6aPjzWgP9DmwVZLM9qE5CFvx1NYCRRhBVr7P+DNhCQyQHPaQMFmYSbDbEzTCRkrFL+A22gIJsCT2mTBdGEWw1hmR4S46rk6bWBggIKPKNNdigg3GiI6BDFERess82uDRQUUWC8NvJogrghoDB+M0rEN6TZtQkoJDygDRQUskmjOThJFGXDd8S5tVmwStm8hR8nsG8FqyzjtQmNaLLdQ8IqXhbNwkqiKAnfEabWxmKFtGRa5E1YwY7WZq9Gs4O5EDeHr7gsicZX9OvnxLe4mbUBV6s79VaIEozVJrrOYgYstf/JbIj6FcOoCaZzYm2Oxmx9o/qwjdVmRYrbTR2oRc6OBhIviaI4fE+YVxtbKRywpvztSG08q3DgK48pbjSrLImiIdSBabUpjX4SzHDItQFmOdG+PmCY0QROEtUeq8kf/aza+NrLCs8bO06bhXvrR6RkVjSemURRgN5y5EqzapPYN1tEwirVpj3YOGA/JAInmthKopiX32l75jm1OQXNtiPBSrVpf7Igk8n90QA7iaIUeu2NeDSnNiteOSTZThilTUJ+G8LLD3VHA6UxcHKL38fL35QptclIyKIqDIzRxorW43q8krobIrCTqEZ3+NfxJ86ozS4bo2l7e6E27Y+VDH6xM5q9kUTxh0d4/dRtRm0KXsmyou8xRhtHelE4dbL1RWMbSRS/HY7KAJ4m1AbYbzZ13ZxAm7aMm/CeL13RZNfIBvjFb19rGzufNideOaVzo3aENjteicLeQ+iIBkojiRIMj1D7NcJ82ix4RZpB4D5Cm4MO771YgcQHP4lq3XWh2jZuPm3WUQshwwhtkvAZRS+GSyua5nS0KDM46/r7z9MmLL+l1XdJHHJofLREG/GrBtX/aEZzNpIoyZy2e/MEPz5Pmwa8vsVTvphqgDaW8WpD2JgSN+pqshe88C7RgMm0ifKOss9rY8QSu0Y0JTWSKFHx+3z3D34ybfgd1TZOro2nff+YxHgziWq9+npiqWrzriPsfW3om9jj2siTqEaiDXglqzYTaiNJon5ffAp4YVdtPlIbc0ubaLi0iggn+TfV5iO1AbE28p7afjfL5IiYqs0naiN/SMnzqPQ7MwJxSrWZUBvJXF/87XPoJBdQbeo/mu9rc+KVJ7URrQfjjCcOL5wzaZPxiv1Xp/uW1isosUSWTDHeXqhV4bO0uVmTwiteHIcboE3kSiwvLuxICfeK36Xxyzj4KG14FfBmzfmjSpn7g/X4gJTtVmFhbzWtn0mbg94z3SRuR8k7vw3wF04USTrFmQfe8MIxkzZLvVbbJj6xTCtIi1Inf5kWOKREefG7tFsIJtLGNu44yfpNuTa79OUmCBaFxhdvXBYv8Nnb4/E+kTZGWtNLTyxBz8LXVHCSJei+kU5xIvX2FfoCMJM2B17JsvQ7SLRpywiy9fBLXzSbvBK+I588kTZedoOvz3y9bpMVi5Lw63WHOA1PyGebSBsYsbklOjNGmyiavN2lX+YFaToVUUCaSBsTBOM0pKe2DqD9eEg2N9m7o4lO9sXMDSXEibSxSFgErRZHaeORcArOcoL+aKysqplQQphIG7Oy7zf/3LZIxrF7cbm1LdIuSadOFOFm0sYy7rfnN2HbuVvC+ZubsAXBwSwBZZwTaWNWjjfyLR+lOUrJPGs2ZjSFn045lBFm0iYixXlOP2Eeqc3J2u52QYoDZjTguOeHeZQCE2lTywsOeG4DaYp8c+284gueHU3kbl5+oBQ/kzaQ8AW3wOjt6uvIt/KHBV9ZBdF4XjoFKGadSZv67JXbYtfhGHGwNubsOjgkhmo0IIlmYy0S3akLv6HglTyTNsZjlRR2+82KOPAonjbb+4OB8o9oDodVrCyalZOGExUO1jZAU2nz/DFkxdaJQ18evLAhgJFOURNOTvGuzKWNCeOPGexi/YxDDytVhkWwiLo9jse5tJF5E8x4beTehHubGFI8fzVs+/15m0wbiTfBjNdG7k24uUEzxcWe9OFk7kc4mzb8su5mxmsjP8ze314KQHG53UqOvaXqbNoY75CB82a4NvLvEbrbDQGlK51yzEIE4JUwnTYmFuymRPOsNsYm7GYFVjRr5zzjMWAj54P4PZ82xiwOu3CLMU9rY2DjDXzji3O4NR5liT976SfUxuSAHYRsBmtTJ67YwQZGqE171tO3EyNmvfyYURsijlAauTYUuz4RzdqfFcTGNIzgS1xTamNM3hy+xW3ZmGe1qRaf6qQFJNGsjNOkYyOd5j+lJtXmK2dI9SLVaRrwtWlz1s1JpNg6oCEg/TadyqLtBkjw5W9oY5cLuSHZBW8YZL+tCX+S1s1nlt8NeCFFv63u12iWUxBN+6pxecGan+TlSl8IfrlCG5pebgLAfgPMR/A9mikaVlEURVEURVEURVEURVEURVEURVEU5Ut7cEgAAAAAIOj/azfYAQAAAAAAAAAAAAAAAAAAgCEsMZPttaKNCgAAAABJRU5ErkJggg==";
|