@flowy-agent-store/protocol 0.1.0-beta.2

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.
@@ -0,0 +1,681 @@
1
+ //#region src/protocol.d.ts
2
+ /**
3
+ * Agent Store App Server Protocol types.
4
+ *
5
+ * The chat surface uses only opaque public conversation/message IDs. Internal
6
+ * execution/session/step/attempt IDs never appear in this module.
7
+ */
8
+ export declare const APP_SERVER_PROTOCOL_VERSION = "2026-08-26";
9
+ export interface JsonRpcRequest {
10
+ jsonrpc: "2.0";
11
+ id: number | string;
12
+ method: string;
13
+ params?: unknown;
14
+ }
15
+ export interface JsonRpcNotification {
16
+ jsonrpc: "2.0";
17
+ method: string;
18
+ params?: unknown;
19
+ }
20
+ export interface JsonRpcResponse {
21
+ jsonrpc: "2.0";
22
+ id: number | string | null;
23
+ result?: unknown;
24
+ error?: WireError;
25
+ }
26
+ export interface JsonRpcEventNotification {
27
+ jsonrpc: "2.0";
28
+ method: "event";
29
+ params: RunEvent;
30
+ }
31
+ export interface ResyncRequiredNotification {
32
+ jsonrpc: "2.0";
33
+ method: "run/resync-required";
34
+ params: {
35
+ run_ids: string[];
36
+ reason: string;
37
+ };
38
+ }
39
+ export interface ConversationEventNotification {
40
+ jsonrpc: "2.0";
41
+ method: "conversation/event";
42
+ params: ConversationEvent;
43
+ }
44
+ export interface ConversationResyncRequiredNotification {
45
+ jsonrpc: "2.0";
46
+ method: "conversation/resync-required";
47
+ params: {
48
+ conversation_ids: string[];
49
+ reason: string;
50
+ };
51
+ }
52
+ export type ServerNotification = JsonRpcEventNotification | ResyncRequiredNotification | ConversationEventNotification | ConversationResyncRequiredNotification;
53
+ export interface WireError {
54
+ code: string;
55
+ message: string;
56
+ retryable: boolean;
57
+ details: Record<string, unknown>;
58
+ request_id?: string | number | null;
59
+ }
60
+ export interface ClientInfo {
61
+ name: string;
62
+ version: string;
63
+ }
64
+ export interface ClientCapabilities {
65
+ events?: boolean;
66
+ approvals?: boolean;
67
+ team_runtime?: boolean;
68
+ artifacts?: boolean;
69
+ }
70
+ export interface InitializeRequest {
71
+ protocol_version: string;
72
+ client: ClientInfo;
73
+ auth?: {
74
+ mode?: string;
75
+ credential?: string;
76
+ };
77
+ capabilities?: ClientCapabilities;
78
+ }
79
+ export interface InitializeResult {
80
+ protocol_version: string;
81
+ server: {
82
+ name: string;
83
+ version: string;
84
+ };
85
+ auth_context: {
86
+ principal_id: string;
87
+ issuer: string;
88
+ audience: string;
89
+ scopes: string[];
90
+ };
91
+ capabilities: Capabilities;
92
+ connection_id: string;
93
+ }
94
+ export interface Capabilities {
95
+ agents: boolean;
96
+ teams: boolean;
97
+ team_runtime: boolean;
98
+ skills: boolean;
99
+ connectors: boolean;
100
+ run_notifications: boolean;
101
+ approvals: boolean;
102
+ artifacts: boolean;
103
+ oauth: boolean;
104
+ imports: boolean;
105
+ installs: boolean;
106
+ marketplaces: boolean;
107
+ store: boolean;
108
+ models: boolean;
109
+ }
110
+ export interface WorkspaceRef {
111
+ id: string;
112
+ }
113
+ export interface WorkspaceRegistration {
114
+ id: string;
115
+ }
116
+ /** Owner-scoped workspace projection returned by `workspace/list` / `workspace/create`. */
117
+ export interface WorkspaceView {
118
+ workspace_id: string;
119
+ /** Display label derived server-side from the directory basename (not a path). */
120
+ name: string;
121
+ /** Absolute canonical path. Only present on the same owner's authenticated connection. */
122
+ canonical_path: string;
123
+ created_at: number;
124
+ updated_at: number;
125
+ }
126
+ export interface WorkspaceCreateInput {
127
+ /** Absolute local directory path chosen by the owner; validated server-side. */
128
+ path: string;
129
+ }
130
+ /** Result of revoking (soft-deleting) an owner's active workspace. */
131
+ export interface WorkspaceRevokeResult {
132
+ workspace_id: string;
133
+ /** false when the workspace is foreign, missing, or already revoked. */
134
+ revoked: boolean;
135
+ }
136
+ /** `GET /api/fs/browse` — one directory listing entry (directories only by default). */
137
+ export interface BrowseEntry {
138
+ name: string;
139
+ path: string;
140
+ isDirectory: boolean;
141
+ isFile: boolean;
142
+ size?: number | null;
143
+ modified?: number | null;
144
+ }
145
+ /** `GET /api/fs/browse` response. */
146
+ export interface BrowseDirectoryResult {
147
+ currentPath: string;
148
+ parentPath?: string | null;
149
+ items: BrowseEntry[];
150
+ canGoUp: boolean;
151
+ truncated: boolean;
152
+ isRoot?: boolean | null;
153
+ }
154
+ export type RunStatus = "planning" | "running" | "completed" | "completed_with_failures" | "failed" | "cancelled" | "paused" | "waiting_input" | "awaiting_approval" | "recovery_required";
155
+ export type MentionKind = "agent" | "skill" | "connector";
156
+ export interface MentionRef {
157
+ kind: MentionKind;
158
+ id: string;
159
+ }
160
+ export interface AgentRunInput {
161
+ agentId: string;
162
+ agentVersion?: string;
163
+ goal?: string;
164
+ input?: {
165
+ text?: string;
166
+ } | Record<string, unknown>;
167
+ workspaceId?: string;
168
+ steps?: unknown[];
169
+ commandId?: string;
170
+ idempotencyKey?: string;
171
+ /** Structured `@` mentions resolved by the composer (docs/agent-store/05 §4.7). */
172
+ mentions?: MentionRef[];
173
+ }
174
+ export interface AgentRunRequestWire {
175
+ agent_id: string;
176
+ agent_version?: string;
177
+ goal?: string;
178
+ input?: unknown;
179
+ work_dir?: string;
180
+ workspace?: WorkspaceRef;
181
+ steps?: unknown[];
182
+ command_id?: string;
183
+ idempotency_key?: string;
184
+ mentions?: MentionRef[];
185
+ }
186
+ export interface RunReceipt {
187
+ run_id: string;
188
+ status: RunStatus;
189
+ version: number;
190
+ preset_revision: number;
191
+ content_digest: string;
192
+ }
193
+ export interface RunView {
194
+ run_id: string;
195
+ status: RunStatus;
196
+ version: number;
197
+ summary?: string | null;
198
+ output_files: string[];
199
+ preset_revision?: number | null;
200
+ content_digest?: string | null;
201
+ }
202
+ export type RunResult = RunView;
203
+ export interface CancelRunInput {
204
+ runId: string;
205
+ expectedVersion: number;
206
+ commandId?: string;
207
+ idempotencyKey?: string;
208
+ }
209
+ export interface SteerRunInput {
210
+ runId: string;
211
+ text: string;
212
+ expectedVersion: number;
213
+ commandId?: string;
214
+ idempotencyKey?: string;
215
+ }
216
+ export interface RunEvent {
217
+ run_id: string;
218
+ sequence: number;
219
+ event_type: string;
220
+ payload: Record<string, unknown>;
221
+ }
222
+ export interface RunEventsQuery {
223
+ runId: string;
224
+ afterSequence?: number;
225
+ limit?: number;
226
+ }
227
+ export interface RunSubscriptionParams {
228
+ run_id: string;
229
+ }
230
+ export interface ProviderWithModel {
231
+ provider_id: string;
232
+ model: string;
233
+ use_model?: string;
234
+ }
235
+ export interface ConversationCreateInput {
236
+ name?: string;
237
+ model?: ProviderWithModel;
238
+ workspaceId?: string;
239
+ reasoningEffort?: string;
240
+ }
241
+ export interface ConversationUpdateInput {
242
+ name?: string;
243
+ model?: ProviderWithModel;
244
+ reasoningEffort?: string;
245
+ }
246
+ export type ReasoningEffort = "low" | "medium" | "high" | "xhigh" | "max";
247
+ export interface ConversationModelOption {
248
+ name: string;
249
+ display_name?: string | null;
250
+ context_limit?: number | null;
251
+ }
252
+ export interface ProviderModelOption {
253
+ name: string;
254
+ models: ConversationModelOption[];
255
+ }
256
+ export interface ConversationModelSelection {
257
+ provider: string;
258
+ model: string;
259
+ }
260
+ export interface ConversationModelOptions {
261
+ default?: ConversationModelSelection | null;
262
+ providers: ProviderModelOption[];
263
+ reasoning_efforts: ReasoningEffort[];
264
+ }
265
+ export interface ConversationView {
266
+ conversation_id: string;
267
+ name: string;
268
+ model: ProviderWithModel;
269
+ status: string;
270
+ created_at: number;
271
+ modified_at: number;
272
+ is_processing: boolean;
273
+ /** Opaque owner-scoped workspace id; absent for legacy chats (UI groups them under "未归类"). */
274
+ workspace_id?: string | null;
275
+ /** Last server-measured context occupancy; null/absent = unknown. */
276
+ context_usage?: ContextUsage | null;
277
+ }
278
+ /** Measured context occupancy for one chat. `percent` is null when the engine reported no window. */
279
+ export interface ContextUsage {
280
+ used_tokens: number;
281
+ window_tokens: number;
282
+ percent?: number | null;
283
+ updated_at: number;
284
+ source: "measured" | string;
285
+ }
286
+ export interface ConversationDeleteResult {
287
+ conversation_id: string;
288
+ deleted: boolean;
289
+ }
290
+ export type ConversationMessageRole = "user" | "assistant" | "activity";
291
+ export interface ConversationMessage {
292
+ message_id: string;
293
+ conversation_id: string;
294
+ role: ConversationMessageRole;
295
+ content: unknown;
296
+ message_type: string;
297
+ status?: string | null;
298
+ created_at: number;
299
+ }
300
+ export interface ConversationSendReceipt {
301
+ conversation_id: string;
302
+ message_id: string;
303
+ turn_id?: string | null;
304
+ accepted: boolean;
305
+ replayed: boolean;
306
+ completed: boolean;
307
+ result_ok?: boolean | null;
308
+ result_text?: string | null;
309
+ result_error?: string | null;
310
+ result_error_code?: string | null;
311
+ result_error_retryable?: boolean | null;
312
+ }
313
+ export interface ConversationEvent {
314
+ conversation_id: string;
315
+ sequence: number;
316
+ event_type: "message.created" | "message.delta" | "message.thinking" | "message.tips" | "message.tool" | "message.error" | "message.activity" | "turn.status" | "context.usage" | string;
317
+ payload: Record<string, unknown>;
318
+ }
319
+ export interface ConversationMessagesQuery {
320
+ conversationId: string;
321
+ page?: number;
322
+ pageSize?: number;
323
+ cursor?: string;
324
+ }
325
+ export interface ConversationMessagesPage {
326
+ items: ConversationMessage[];
327
+ /** Exact server-computed flag: `true` when an older page still exists. */
328
+ has_more: boolean;
329
+ }
330
+ export interface ConversationSubscription {
331
+ conversation_id: string;
332
+ }
333
+ export type CompatibilityStatus = "compatible" | "compatible-with-adapter" | "manual-review" | "unsupported" | "pending-legal-review";
334
+ export interface SkillSummary {
335
+ id: string;
336
+ name: string;
337
+ description?: string | null;
338
+ version: string;
339
+ source: string;
340
+ compatibility_status: CompatibilityStatus;
341
+ enabled: boolean;
342
+ required_connectors: string[];
343
+ }
344
+ /** `skill/get` — the raw SKILL.md body and internal routing rules are never returned. */
345
+ export interface SkillDetail extends SkillSummary {
346
+ mode: string;
347
+ invocation_policy: string;
348
+ instructions_summary?: string | null;
349
+ }
350
+ export type ConnectorStatus = "installed" | "configured" | "authorization_required" | "authenticated" | "connected" | "degraded" | "error" | "reauthorization_required";
351
+ export interface ConnectorSummary {
352
+ id: string;
353
+ name: string;
354
+ description?: string | null;
355
+ kind: string;
356
+ transport_summary: string;
357
+ auth_mode: string;
358
+ enabled: boolean;
359
+ status: ConnectorStatus;
360
+ }
361
+ export interface ConnectorTool {
362
+ name: string;
363
+ description?: string | null;
364
+ }
365
+ export interface OAuthStatusView {
366
+ state: "authenticated" | "not_authenticated" | "reauthorization_required" | string;
367
+ error?: string | null;
368
+ }
369
+ /** `connector/get` — tools are namespaced public names only. */
370
+ export interface ConnectorDetail extends ConnectorSummary {
371
+ tool_filter?: string | null;
372
+ tools: ConnectorTool[];
373
+ auth_status?: OAuthStatusView | null;
374
+ source: string;
375
+ compatibility_status: CompatibilityStatus;
376
+ }
377
+ export interface ConnectorStatusView {
378
+ connector_id: string;
379
+ status: ConnectorStatus;
380
+ auth_status?: OAuthStatusView | null;
381
+ last_error?: string | null;
382
+ }
383
+ export interface ConnectorProbeResult {
384
+ connector_id: string;
385
+ success: boolean;
386
+ tools?: ConnectorTool[] | null;
387
+ error?: string | null;
388
+ code?: string | null;
389
+ }
390
+ export interface OAuthStartResult {
391
+ connector_id: string;
392
+ state: "started" | "error" | string;
393
+ error?: string | null;
394
+ }
395
+ export interface ConnectorQueryParams {
396
+ connector_id: string;
397
+ }
398
+ export interface SkillQueryParams {
399
+ skill_id: string;
400
+ }
401
+ export type CompatibilityTriple = {
402
+ /** Wire values: compatible | compatible_with_adapter | manual_review | unsupported | pending_legal_review. */
403
+ semantic_status: string;
404
+ /** not-verified | adapter-verified | runtime-verified | release-eligible. */
405
+ runtime_status: string;
406
+ /** local-only (V1). */
407
+ distribution_status: string;
408
+ reasons: string[];
409
+ };
410
+ export type ImportSourceKind = "codebuddy-plugin" | "workbuddy-skill-market" | "workbuddy-connector-market" | "workbuddy-cli-connector";
411
+ export interface ImportRequest {
412
+ /** Absolute local directory path on the trusted host; validated server-side. */
413
+ source_path: string;
414
+ source_kind: ImportSourceKind;
415
+ }
416
+ export type ImportStatus = "completed" | "completed-with-warnings" | "blocked" | "failed";
417
+ export interface ImportResult {
418
+ snapshot_id: string;
419
+ name: string;
420
+ version: string;
421
+ source_kind: string;
422
+ status: ImportStatus;
423
+ content_digest: string;
424
+ component_status: CompatibilityTriple;
425
+ component_count: number;
426
+ imported_at: number;
427
+ /** true when an identical digest was already imported (idempotent reuse). */
428
+ reused: boolean;
429
+ warnings: string[];
430
+ errors: string[];
431
+ }
432
+ export interface ImportSummary {
433
+ snapshot_id: string;
434
+ name: string;
435
+ version: string;
436
+ source_kind: string;
437
+ status: ImportStatus;
438
+ component_count: number;
439
+ imported_at: number;
440
+ }
441
+ export interface ImportComponent {
442
+ id: string;
443
+ kind: string;
444
+ name: string;
445
+ compatibility: CompatibilityTriple;
446
+ warnings: string[];
447
+ }
448
+ export interface ImportDetail extends ImportSummary {
449
+ content_digest: string;
450
+ component_status: CompatibilityTriple;
451
+ warnings: string[];
452
+ errors: string[];
453
+ components: ImportComponent[];
454
+ }
455
+ export interface InstallRequest {
456
+ snapshot_id: string;
457
+ }
458
+ export type InstallState = "not-installed" | "installed" | "disabled";
459
+ export interface InstallComponent {
460
+ id: string;
461
+ kind: string;
462
+ name: string;
463
+ state: InstallState;
464
+ runtime_location?: string | null;
465
+ preset_id?: string | null;
466
+ }
467
+ export interface InstallResult {
468
+ snapshot_id: string;
469
+ name: string;
470
+ version: string;
471
+ installed_count: number;
472
+ skipped: string[];
473
+ warnings: string[];
474
+ errors: string[];
475
+ }
476
+ export interface InstallStatus {
477
+ snapshot_id: string;
478
+ components: InstallComponent[];
479
+ }
480
+ /** Wire-marketplace source kinds (Phase A: directory only). */
481
+ export type MarketplaceSourceKind = "directory" | "github" | "git" | "url";
482
+ export interface MarketplaceAddRequest {
483
+ name?: string;
484
+ source_kind: MarketplaceSourceKind;
485
+ /** Local directory path / owner/repo / Git URL / HTTP URL. */
486
+ source: string;
487
+ }
488
+ export interface MarketplaceSummary {
489
+ marketplace_id: string;
490
+ name: string;
491
+ description?: string | null;
492
+ source_kind: string;
493
+ version?: string | null;
494
+ auto_update: boolean;
495
+ enabled: boolean;
496
+ entry_count: number;
497
+ added_at: number;
498
+ }
499
+ export interface MarketplaceEntry {
500
+ name: string;
501
+ source_kind: string;
502
+ source: string;
503
+ version?: string | null;
504
+ description?: string | null;
505
+ keywords: string[];
506
+ category?: string | null;
507
+ }
508
+ export interface MarketplaceDetail extends MarketplaceSummary {
509
+ entries: MarketplaceEntry[];
510
+ }
511
+ export interface MarketplaceRemoveResult {
512
+ marketplace_id: string;
513
+ /** Snapshot ids installed from this marketplace, uninstalled by the cascade. */
514
+ snapshots: string[];
515
+ uninstalled_components: string[];
516
+ warnings: string[];
517
+ }
518
+ export interface MarketplaceRefreshResult {
519
+ marketplace_id: string;
520
+ /** true when the remote revision changed and the projection was rebuilt. */
521
+ changed: boolean;
522
+ resolved_revision: string;
523
+ entry_count: number;
524
+ warnings: string[];
525
+ }
526
+ export type StoreItemKind = "agent" | "team" | "skill" | "connector";
527
+ /** One store item: a marketplace entry with display fidelity + install state. */
528
+ export interface StoreItem {
529
+ /** Stable composite id: `<marketplace_id>/<entry_name>`. */
530
+ id: string;
531
+ marketplace_id: string;
532
+ marketplace_name: string;
533
+ entry_name: string;
534
+ kind: StoreItemKind;
535
+ /** Display name: `displayName` (localized) when present, else entry name. */
536
+ name: string;
537
+ display_name?: LocalizedText | null;
538
+ profession?: LocalizedText | null;
539
+ description?: string | null;
540
+ display_description?: LocalizedText | null;
541
+ /** Empty arrays are omitted on the wire (`skip_serializing_if`).
542
+ * Treat as optional: `item.tags ?? []`. */
543
+ tags?: LocalizedText[] | null;
544
+ quick_prompts?: LocalizedText[] | null;
545
+ /** Public avatar URL (store asset endpoint); relative to the API host. */
546
+ avatar_url?: string | null;
547
+ version: string;
548
+ source_kind: string;
549
+ installed: boolean;
550
+ update_available: boolean;
551
+ snapshot_id?: string | null;
552
+ installed_version?: string | null;
553
+ }
554
+ export interface StoreList {
555
+ items: StoreItem[];
556
+ }
557
+ /** One model in the public catalog (`models/list`, REQ-PAR-05b). Provider
558
+ * credentials, base URLs and health internals never cross this projection. */
559
+ export interface ModelSummary {
560
+ provider_id: string;
561
+ provider_name: string;
562
+ model: string;
563
+ /** Provider-supplied display label when configured. */
564
+ display_name?: string | null;
565
+ /** True when an unbound preset resolves to this provider/model. */
566
+ is_default: boolean;
567
+ }
568
+ /** `models/list` response: the public model directory over enabled providers. */
569
+ export interface ModelList {
570
+ items: ModelSummary[];
571
+ }
572
+ export interface StoreInstallResult {
573
+ marketplace_id: string;
574
+ entry_name: string;
575
+ snapshot_id: string;
576
+ version: string;
577
+ /** true when the components were already registered (no-op install). */
578
+ reused: boolean;
579
+ installed_count: number;
580
+ warnings: string[];
581
+ errors: string[];
582
+ }
583
+ export interface LocalizedText {
584
+ en?: string | null;
585
+ zh?: string | null;
586
+ }
587
+ export interface AgentSummary {
588
+ id: string;
589
+ version: string;
590
+ name: string;
591
+ /** Installed preset id; present after `install/*` of the snapshot (Phase 2). */
592
+ preset_id?: string | null;
593
+ description?: string | null;
594
+ skills: string[];
595
+ connectors: string[];
596
+ model_summary?: string | null;
597
+ tool_policy_summary?: string | null;
598
+ source: string;
599
+ compatibility_status: CompatibilityStatus;
600
+ display_name?: LocalizedText | null;
601
+ profession?: LocalizedText | null;
602
+ /** Public avatar URL (server-asset endpoint); relative to the API host. */
603
+ avatar_url?: string | null;
604
+ }
605
+ /** `agent/get` — structured frontmatter fields only; raw prompts never cross. */
606
+ export interface AgentDetail extends AgentSummary {
607
+ effort?: string | null;
608
+ max_turns?: number | null;
609
+ disallowed_tools: string[];
610
+ memory?: string | null;
611
+ background?: string | null;
612
+ isolation?: string | null;
613
+ permission_mode_ignored: boolean;
614
+ display_description?: LocalizedText | null;
615
+ quick_prompts?: LocalizedText[];
616
+ tags?: LocalizedText[];
617
+ default_init_prompt?: LocalizedText | null;
618
+ expert_type?: string | null;
619
+ category_id?: string | null;
620
+ }
621
+ export interface TeamSummary {
622
+ id: string;
623
+ version: string;
624
+ name: string;
625
+ description?: string | null;
626
+ lead_agent_id: string;
627
+ member_agent_ids: string[];
628
+ source: string;
629
+ compatibility_status: CompatibilityStatus;
630
+ }
631
+ export interface TeamDetail extends TeamSummary {
632
+ planner_policy: string;
633
+ routing_constraints: string[];
634
+ workflow_limits: Record<string, unknown>;
635
+ team_runtime_capabilities: string[];
636
+ }
637
+ //#endregion
638
+ //#region src/errors.d.ts
639
+ export type ErrorCode = string;
640
+ export declare class AppServerError extends Error {
641
+ readonly code: ErrorCode;
642
+ readonly requestId?: string | number | null;
643
+ readonly retryable: boolean;
644
+ readonly details: Record<string, unknown>;
645
+ constructor(wire: WireError);
646
+ }
647
+ export type TransportPhase = "connect" | "send" | "receive" | "close";
648
+ export declare class TransportError extends Error {
649
+ readonly phase: TransportPhase;
650
+ readonly retryable: boolean;
651
+ readonly cause?: unknown;
652
+ constructor(phase: TransportPhase, message: string, options?: {
653
+ retryable?: boolean;
654
+ cause?: unknown;
655
+ });
656
+ }
657
+ export type ProtocolErrorKind = "invalid_message" | "version_mismatch" | "unexpected_response";
658
+ export declare class ProtocolError extends Error {
659
+ readonly kind: ProtocolErrorKind;
660
+ constructor(kind: ProtocolErrorKind, message: string);
661
+ }
662
+ export declare class RequestTimeoutError extends Error {
663
+ readonly method: string;
664
+ readonly timeoutMs: number;
665
+ constructor(method: string, timeoutMs: number);
666
+ }
667
+ export declare function isAppServerError(error: unknown): error is AppServerError;
668
+ export declare function isRetryableTransportError(error: unknown): boolean;
669
+ /**
670
+ * Single human-readable rendering of a caught error.
671
+ *
672
+ * Every UI surface shares this one: a bare `Error` contributes only its
673
+ * message, while a protocol error contributes its stable `code` plus the
674
+ * server's retry hint. Components must not grow their own variants — a
675
+ * second spelling means the two drift apart on the retry hint.
676
+ */
677
+ export declare function formatError(error: unknown): string;
678
+ /** Whether the caught error is retryable — used by UI surfaces to append a
679
+ * localized "(可重试)" hint without baking wording into this lib. */
680
+ export declare function isRetryableError(error: unknown): boolean;
681
+ //#endregion