@raisindb/client 0.1.0 → 0.1.1
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 +313 -70
- package/dist/chunk-ASOWGNQZ.mjs +532 -0
- package/dist/chunk-ASOWGNQZ.mjs.map +1 -0
- package/dist/chunk-FYWYSNQG.mjs +190 -0
- package/dist/chunk-FYWYSNQG.mjs.map +1 -0
- package/dist/chunk-NOPZAMIV.mjs +1427 -0
- package/dist/chunk-NOPZAMIV.mjs.map +1 -0
- package/dist/chunk-T2ZSNYFG.mjs +248 -0
- package/dist/chunk-T2ZSNYFG.mjs.map +1 -0
- package/dist/chunk-TPZNJNZR.mjs +866 -0
- package/dist/chunk-TPZNJNZR.mjs.map +1 -0
- package/dist/chunk-ZCRYQCXS.mjs +170 -0
- package/dist/chunk-ZCRYQCXS.mjs.map +1 -0
- package/dist/conversation-list-store-D-W6_nsv.d.mts +8447 -0
- package/dist/conversation-list-store-D-W6_nsv.d.ts +8447 -0
- package/dist/index-B4CqE3Cb.d.ts +301 -0
- package/dist/index-CeBRaSop.d.mts +301 -0
- package/dist/index.d.mts +72 -6109
- package/dist/index.d.ts +72 -6109
- package/dist/index.js +3123 -257
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2745 -3185
- package/dist/index.mjs.map +1 -0
- package/dist/react.d.mts +4 -0
- package/dist/react.d.ts +4 -0
- package/dist/react.js +2569 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +9 -0
- package/dist/react.mjs.map +1 -0
- package/dist/svelte-sql-CQoB4FEm.d.mts +52 -0
- package/dist/svelte-sql-CTXjkz0R.d.ts +52 -0
- package/dist/svelte.d.mts +288 -0
- package/dist/svelte.d.ts +288 -0
- package/dist/svelte.js +2482 -0
- package/dist/svelte.js.map +1 -0
- package/dist/svelte.mjs +25 -0
- package/dist/svelte.mjs.map +1 -0
- package/dist/vue.d.mts +197 -0
- package/dist/vue.d.ts +197 -0
- package/dist/vue.js +1510 -0
- package/dist/vue.js.map +1 -0
- package/dist/vue.mjs +9 -0
- package/dist/vue.mjs.map +1 -0
- package/package.json +18 -3
package/dist/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __typeError = (msg) => {
|
|
7
|
+
throw TypeError(msg);
|
|
8
|
+
};
|
|
8
9
|
var __export = (target, all) => {
|
|
9
10
|
for (var name in all)
|
|
10
11
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -17,19 +18,17 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
18
|
}
|
|
18
19
|
return to;
|
|
19
20
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
21
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
23
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
24
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
25
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
29
26
|
|
|
30
27
|
// src/index.ts
|
|
31
|
-
var
|
|
32
|
-
__export(
|
|
28
|
+
var src_exports = {};
|
|
29
|
+
__export(src_exports, {
|
|
30
|
+
AgentRunsApi: () => AgentRunsApi,
|
|
31
|
+
AgentRunsWsApi: () => AgentRunsWsApi,
|
|
33
32
|
AllNodeEventTypes: () => AllNodeEventTypes,
|
|
34
33
|
Archetypes: () => Archetypes,
|
|
35
34
|
AuthManager: () => AuthManager,
|
|
@@ -49,6 +48,7 @@ __export(index_exports, {
|
|
|
49
48
|
DEFAULT_RETRY_BASE_DELAY: () => DEFAULT_RETRY_BASE_DELAY,
|
|
50
49
|
DEFAULT_RETRY_MAX_DELAY: () => DEFAULT_RETRY_MAX_DELAY,
|
|
51
50
|
DEFAULT_RETRY_OPTIONS: () => DEFAULT_RETRY_OPTIONS,
|
|
51
|
+
DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS: () => DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS,
|
|
52
52
|
Database: () => Database,
|
|
53
53
|
ElementTypes: () => ElementTypes,
|
|
54
54
|
EventHandler: () => EventHandler,
|
|
@@ -56,19 +56,27 @@ __export(index_exports, {
|
|
|
56
56
|
FlowClient: () => FlowClient,
|
|
57
57
|
FlowsApi: () => FlowsApi,
|
|
58
58
|
FunctionsApi: () => FunctionsApi,
|
|
59
|
+
HttpArchetypes: () => HttpArchetypes,
|
|
59
60
|
HttpDatabase: () => HttpDatabase,
|
|
61
|
+
HttpElementTypes: () => HttpElementTypes,
|
|
60
62
|
HttpFunctionsApi: () => HttpFunctionsApi,
|
|
63
|
+
HttpNodeTypes: () => HttpNodeTypes,
|
|
61
64
|
HttpWorkspaceClient: () => HttpWorkspaceClient,
|
|
65
|
+
IdentityAuthApi: () => IdentityAuthApi,
|
|
66
|
+
InboxApi: () => InboxApi,
|
|
62
67
|
LocalStorageTokenStorage: () => LocalStorageTokenStorage,
|
|
63
68
|
LogLevel: () => LogLevel,
|
|
69
|
+
McpClient: () => McpClient,
|
|
64
70
|
MemoryTokenStorage: () => MemoryTokenStorage,
|
|
65
71
|
NodeBuilder: () => NodeBuilder,
|
|
72
|
+
NodeDevApi: () => NodeDevApi,
|
|
66
73
|
NodeEventType: () => NodeEventType,
|
|
67
74
|
NodeFileSource: () => NodeFileSource,
|
|
68
75
|
NodeHelpers: () => NodeHelpers,
|
|
69
76
|
NodeOperations: () => NodeOperations,
|
|
70
77
|
NodeTypes: () => NodeTypes,
|
|
71
78
|
RAISIN_CONTEXT_KEY: () => RAISIN_CONTEXT_KEY,
|
|
79
|
+
RETRY_STRATEGIES: () => RETRY_STRATEGIES,
|
|
72
80
|
RaisinAbortError: () => RaisinAbortError,
|
|
73
81
|
RaisinAuthError: () => RaisinAuthError,
|
|
74
82
|
RaisinClient: () => RaisinClient,
|
|
@@ -83,6 +91,7 @@ __export(index_exports, {
|
|
|
83
91
|
ResponseStatus: () => ResponseStatus,
|
|
84
92
|
RetryBuilder: () => RetryBuilder,
|
|
85
93
|
SSEClient: () => SSEClient,
|
|
94
|
+
SchedulerApi: () => SchedulerApi,
|
|
86
95
|
SqlQuery: () => SqlQuery,
|
|
87
96
|
Tags: () => Tags,
|
|
88
97
|
Transaction: () => Transaction,
|
|
@@ -90,6 +99,7 @@ __export(index_exports, {
|
|
|
90
99
|
UploadErrorCode: () => UploadErrorCode,
|
|
91
100
|
UploadManager: () => UploadManager,
|
|
92
101
|
Uploader: () => Uploader,
|
|
102
|
+
VisitorChatError: () => VisitorChatError,
|
|
93
103
|
WorkspaceClient: () => WorkspaceClient,
|
|
94
104
|
WorkspaceManager: () => WorkspaceManager,
|
|
95
105
|
calculateChunkCount: () => calculateChunkCount,
|
|
@@ -104,35 +114,45 @@ __export(index_exports, {
|
|
|
104
114
|
createFlowAdapter: () => createFlowAdapter,
|
|
105
115
|
createLoader: () => createLoader,
|
|
106
116
|
createRaisinReact: () => createRaisinReact,
|
|
117
|
+
createRaisinVue: () => createRaisinVue,
|
|
107
118
|
createRequestScopedClient: () => createRequestScopedClient,
|
|
108
119
|
createSSRClient: () => createSSRClient,
|
|
109
120
|
createSqlAdapter: () => createSqlAdapter,
|
|
110
121
|
createSqlHandler: () => createSqlHandler,
|
|
111
122
|
createSubscriptionAdapter: () => createSubscriptionAdapter,
|
|
112
123
|
decodeMessage: () => decodeMessage,
|
|
124
|
+
defaultVisitorSessionStore: () => defaultVisitorSessionStore,
|
|
113
125
|
encodeMessage: () => encodeMessage,
|
|
114
126
|
getChunkRange: () => getChunkRange,
|
|
115
127
|
getContentType: () => getContentType,
|
|
116
128
|
getErrorMessage: () => getErrorMessage,
|
|
117
129
|
getLogLevel: () => getLogLevel,
|
|
118
130
|
getLoggerConfig: () => getLoggerConfig,
|
|
131
|
+
getRefDisplayName: () => getRefDisplayName,
|
|
132
|
+
getRefPath: () => getRefPath,
|
|
119
133
|
isEventMessage: () => isEventMessage,
|
|
134
|
+
isFlowDefinitionContainer: () => isFlowDefinitionContainer,
|
|
135
|
+
isFlowDefinitionStep: () => isFlowDefinitionStep,
|
|
120
136
|
isHttpClient: () => isHttpClient,
|
|
121
137
|
isResponseEnvelope: () => isResponseEnvelope,
|
|
122
138
|
isRetryableError: () => isRetryableError,
|
|
123
139
|
isTerminalEvent: () => isTerminalEvent,
|
|
124
140
|
isWebSocketClient: () => isWebSocketClient,
|
|
125
141
|
logger: () => logger,
|
|
142
|
+
newControlId: () => newControlId,
|
|
143
|
+
normalizeHomePath: () => normalizeHomePath,
|
|
126
144
|
projectPlansFromMessages: () => projectPlansFromMessages,
|
|
145
|
+
readTokensFromFragment: () => readTokensFromFragment,
|
|
127
146
|
rowsToObjects: () => rowsToObjects,
|
|
128
147
|
setLogLevel: () => setLogLevel,
|
|
129
148
|
sleep: () => sleep,
|
|
149
|
+
subjectOf: () => subjectOf,
|
|
130
150
|
useConversation: () => useConversation,
|
|
131
151
|
useConversationList: () => useConversationList,
|
|
132
152
|
useFlow: () => useFlow,
|
|
133
153
|
withRetry: () => withRetry
|
|
134
154
|
});
|
|
135
|
-
module.exports = __toCommonJS(
|
|
155
|
+
module.exports = __toCommonJS(src_exports);
|
|
136
156
|
|
|
137
157
|
// src/client.ts
|
|
138
158
|
var import_events4 = require("events");
|
|
@@ -326,7 +346,8 @@ var Connection = class extends import_events.EventEmitter {
|
|
|
326
346
|
autoReconnect: options.autoReconnect ?? true,
|
|
327
347
|
heartbeatInterval: options.heartbeatInterval ?? 3e4,
|
|
328
348
|
heartbeatTimeout: options.heartbeatTimeout ?? 5e3,
|
|
329
|
-
protocols: options.protocols
|
|
349
|
+
protocols: options.protocols,
|
|
350
|
+
headers: options.headers
|
|
330
351
|
};
|
|
331
352
|
this.reconnectManager = new ReconnectManager(options.reconnectOptions);
|
|
332
353
|
this.WebSocketImpl = getWebSocketImpl();
|
|
@@ -342,10 +363,10 @@ var Connection = class extends import_events.EventEmitter {
|
|
|
342
363
|
this.setState("connecting" /* Connecting */);
|
|
343
364
|
return new Promise((resolve, reject) => {
|
|
344
365
|
try {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
this.options.
|
|
348
|
-
);
|
|
366
|
+
const isBrowserWebSocket = typeof WebSocket !== "undefined" && this.WebSocketImpl === WebSocket;
|
|
367
|
+
this.ws = isBrowserWebSocket || !this.options.headers ? new this.WebSocketImpl(this.url, this.options.protocols) : new this.WebSocketImpl(this.url, this.options.protocols, {
|
|
368
|
+
headers: this.options.headers
|
|
369
|
+
});
|
|
349
370
|
this.ws.binaryType = "arraybuffer";
|
|
350
371
|
this.ws.onopen = () => {
|
|
351
372
|
this.setState("connected" /* Connected */);
|
|
@@ -434,6 +455,16 @@ var Connection = class extends import_events.EventEmitter {
|
|
|
434
455
|
isConnected() {
|
|
435
456
|
return this.state === "connected" /* Connected */;
|
|
436
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* Whether auto-reconnect is enabled for this connection.
|
|
460
|
+
*
|
|
461
|
+
* Used by the client to decide whether requests issued while the
|
|
462
|
+
* connection is down can be queued (the connection will come back) or
|
|
463
|
+
* should fail immediately.
|
|
464
|
+
*/
|
|
465
|
+
isAutoReconnectEnabled() {
|
|
466
|
+
return this.options.autoReconnect;
|
|
467
|
+
}
|
|
437
468
|
/**
|
|
438
469
|
* Reconnect to the server
|
|
439
470
|
*/
|
|
@@ -677,7 +708,7 @@ var LocalStorageTokenStorage = class {
|
|
|
677
708
|
function isJwtCredentials(credentials) {
|
|
678
709
|
return "type" in credentials && credentials.type === "jwt";
|
|
679
710
|
}
|
|
680
|
-
var AuthManager = class {
|
|
711
|
+
var AuthManager = class _AuthManager {
|
|
681
712
|
constructor(storage) {
|
|
682
713
|
this.expiresAt = null;
|
|
683
714
|
this._storage = storage ?? new MemoryTokenStorage();
|
|
@@ -700,6 +731,31 @@ var AuthManager = class {
|
|
|
700
731
|
this.expiresAt = Date.now() + expiresInMs;
|
|
701
732
|
this.scheduleTokenRefresh(expiresInMs);
|
|
702
733
|
}
|
|
734
|
+
/**
|
|
735
|
+
* Learn a bare token's expiry FROM THE TOKEN.
|
|
736
|
+
*
|
|
737
|
+
* `setTokens` is the only other writer of `expiresAt`, and it needs a whole
|
|
738
|
+
* `AuthenticateResponse`. Several paths hold nothing but a JWT string — a
|
|
739
|
+
* session restored from storage, `authenticate({type:'jwt'})`,
|
|
740
|
+
* `setIdentityTokens(token)` — and left `expiresAt` null. `isTokenExpired()`
|
|
741
|
+
* reads null as EXPIRED, so `isAuthenticated()` answered false for the whole
|
|
742
|
+
* life of every restored session, i.e. every page load after the first.
|
|
743
|
+
*
|
|
744
|
+
* It failed silently, and it had already been routed around three times
|
|
745
|
+
* rather than fixed: both HTTP send paths were made to stop consulting
|
|
746
|
+
* `isAuthenticated()` (see `request()` and `mcpStream()` in http-client),
|
|
747
|
+
* and Studio grew its own token parse to tell a 404 apart from a logged-out
|
|
748
|
+
* read. This is the missing writer those workarounds existed for.
|
|
749
|
+
*
|
|
750
|
+
* Same 60s safety margin as `setTokens`, so both writers agree on when a
|
|
751
|
+
* token stops counting as good.
|
|
752
|
+
*
|
|
753
|
+
* @param token - A JWT held in storage or handed to us
|
|
754
|
+
*/
|
|
755
|
+
adoptToken(token) {
|
|
756
|
+
const exp = _AuthManager.parseToken(token)?.exp;
|
|
757
|
+
this.expiresAt = typeof exp === "number" ? exp * 1e3 - 6e4 : null;
|
|
758
|
+
}
|
|
703
759
|
/**
|
|
704
760
|
* Get current access token
|
|
705
761
|
*/
|
|
@@ -818,6 +874,7 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
|
|
|
818
874
|
RequestType2["NodeUpdate"] = "node_update";
|
|
819
875
|
RequestType2["NodeDelete"] = "node_delete";
|
|
820
876
|
RequestType2["NodeGet"] = "node_get";
|
|
877
|
+
RequestType2["NodeGetByLocalizedPath"] = "node_get_by_localized_path";
|
|
821
878
|
RequestType2["NodeQuery"] = "node_query";
|
|
822
879
|
RequestType2["NodeQueryByPath"] = "node_query_by_path";
|
|
823
880
|
RequestType2["NodeQueryByProperty"] = "node_query_by_property";
|
|
@@ -831,6 +888,7 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
|
|
|
831
888
|
RequestType2["NodeReorder"] = "node_reorder";
|
|
832
889
|
RequestType2["NodeMoveChildBefore"] = "node_move_child_before";
|
|
833
890
|
RequestType2["NodeMoveChildAfter"] = "node_move_child_after";
|
|
891
|
+
RequestType2["NodeApplyChildOrder"] = "node_apply_child_order";
|
|
834
892
|
RequestType2["PropertyGet"] = "property_get";
|
|
835
893
|
RequestType2["PropertyUpdate"] = "property_update";
|
|
836
894
|
RequestType2["RelationAdd"] = "relation_add";
|
|
@@ -850,6 +908,8 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
|
|
|
850
908
|
RequestType2["BranchUpdateHead"] = "branch_update_head";
|
|
851
909
|
RequestType2["BranchMerge"] = "branch_merge";
|
|
852
910
|
RequestType2["BranchCompare"] = "branch_compare";
|
|
911
|
+
RequestType2["BranchDiff"] = "branch_diff";
|
|
912
|
+
RequestType2["BranchCopyNodes"] = "branch_copy_nodes";
|
|
853
913
|
RequestType2["TagCreate"] = "tag_create";
|
|
854
914
|
RequestType2["TagGet"] = "tag_get";
|
|
855
915
|
RequestType2["TagList"] = "tag_list";
|
|
@@ -870,6 +930,7 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
|
|
|
870
930
|
RequestType2["ArchetypeDelete"] = "archetype_delete";
|
|
871
931
|
RequestType2["ArchetypePublish"] = "archetype_publish";
|
|
872
932
|
RequestType2["ArchetypeUnpublish"] = "archetype_unpublish";
|
|
933
|
+
RequestType2["ArchetypeGetResolved"] = "archetype_get_resolved";
|
|
873
934
|
RequestType2["ElementTypeCreate"] = "element_type_create";
|
|
874
935
|
RequestType2["ElementTypeGet"] = "element_type_get";
|
|
875
936
|
RequestType2["ElementTypeList"] = "element_type_list";
|
|
@@ -877,6 +938,7 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
|
|
|
877
938
|
RequestType2["ElementTypeDelete"] = "element_type_delete";
|
|
878
939
|
RequestType2["ElementTypePublish"] = "element_type_publish";
|
|
879
940
|
RequestType2["ElementTypeUnpublish"] = "element_type_unpublish";
|
|
941
|
+
RequestType2["ElementTypeGetResolved"] = "element_type_get_resolved";
|
|
880
942
|
RequestType2["FullTextSearch"] = "full_text_search";
|
|
881
943
|
RequestType2["VectorSearch"] = "vector_search";
|
|
882
944
|
RequestType2["Subscribe"] = "subscribe";
|
|
@@ -899,8 +961,23 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
|
|
|
899
961
|
RequestType2["FlowCancel"] = "flow_cancel";
|
|
900
962
|
RequestType2["FlowSubscribeEvents"] = "flow_subscribe_events";
|
|
901
963
|
RequestType2["FlowUnsubscribeEvents"] = "flow_unsubscribe_events";
|
|
964
|
+
RequestType2["AgentRunCreate"] = "agent_run_create";
|
|
965
|
+
RequestType2["AgentRunGet"] = "agent_run_get";
|
|
966
|
+
RequestType2["AgentRunBySubject"] = "agent_run_by_subject";
|
|
967
|
+
RequestType2["AgentRunList"] = "agent_run_list";
|
|
968
|
+
RequestType2["AgentRunEvents"] = "agent_run_events";
|
|
969
|
+
RequestType2["AgentRunSubscribe"] = "agent_run_subscribe";
|
|
970
|
+
RequestType2["AgentRunUnsubscribe"] = "agent_run_unsubscribe";
|
|
971
|
+
RequestType2["AgentRunControl"] = "agent_run_control";
|
|
972
|
+
RequestType2["AgentRunChildren"] = "agent_run_children";
|
|
902
973
|
RequestType2["FunctionInvoke"] = "function_invoke";
|
|
903
974
|
RequestType2["FunctionInvokeSync"] = "function_invoke_sync";
|
|
975
|
+
RequestType2["ScheduledInvocationCreate"] = "scheduled_invocation_create";
|
|
976
|
+
RequestType2["ScheduledInvocationCancel"] = "scheduled_invocation_cancel";
|
|
977
|
+
RequestType2["ScheduledInvocationList"] = "scheduled_invocation_list";
|
|
978
|
+
RequestType2["ScheduledInvocationGet"] = "scheduled_invocation_get";
|
|
979
|
+
RequestType2["VisitorChatStart"] = "visitor_chat_start";
|
|
980
|
+
RequestType2["VisitorChatSend"] = "visitor_chat_send";
|
|
904
981
|
return RequestType2;
|
|
905
982
|
})(RequestType || {});
|
|
906
983
|
var ResponseStatus = /* @__PURE__ */ ((ResponseStatus2) => {
|
|
@@ -1010,6 +1087,14 @@ var EventHandler = class {
|
|
|
1010
1087
|
* If a subscription with identical filters already exists, the callback
|
|
1011
1088
|
* is added to the existing subscription instead of creating a new one.
|
|
1012
1089
|
*
|
|
1090
|
+
* Path filter semantics (matched server-side):
|
|
1091
|
+
* - A plain path matches **only that exact node** — there is no implicit
|
|
1092
|
+
* prefix matching.
|
|
1093
|
+
* - `*` matches exactly one path segment (e.g. `/inbox/*` matches
|
|
1094
|
+
* `/inbox/a` but not `/inbox/a/b`).
|
|
1095
|
+
* - `**` matches recursively (e.g. `/inbox/**` matches all descendants
|
|
1096
|
+
* of `/inbox`, but not `/inbox` itself).
|
|
1097
|
+
*
|
|
1013
1098
|
* @param filters - Event filters
|
|
1014
1099
|
* @param callback - Callback function for events
|
|
1015
1100
|
* @returns Subscription object
|
|
@@ -1176,7 +1261,8 @@ var EventHandler = class {
|
|
|
1176
1261
|
this.filterSubscriptions.set(hash, {
|
|
1177
1262
|
serverId: subscriptionId,
|
|
1178
1263
|
filters: {},
|
|
1179
|
-
callbacks: /* @__PURE__ */ new Set([callback])
|
|
1264
|
+
callbacks: /* @__PURE__ */ new Set([callback]),
|
|
1265
|
+
synthetic: true
|
|
1180
1266
|
});
|
|
1181
1267
|
this.subscriptions.set(subscriptionId, hash);
|
|
1182
1268
|
this.callbackToHash.set(callback, hash);
|
|
@@ -1200,14 +1286,30 @@ var EventHandler = class {
|
|
|
1200
1286
|
* Restore subscriptions after reconnect
|
|
1201
1287
|
*
|
|
1202
1288
|
* Re-subscribes to the server with the original filters for all active
|
|
1203
|
-
* subscription entries.
|
|
1289
|
+
* subscription entries. Failed entries are kept (with their callbacks)
|
|
1290
|
+
* and marked pending so a retry call can re-attempt them without
|
|
1291
|
+
* duplicating already-restored subscriptions.
|
|
1292
|
+
*
|
|
1293
|
+
* @param options.retry - When true, only re-attempts entries that failed
|
|
1294
|
+
* in a previous call (instead of re-subscribing everything)
|
|
1295
|
+
* @throws Error if any subscription could not be restored
|
|
1204
1296
|
*/
|
|
1205
|
-
async restoreSubscriptions() {
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1297
|
+
async restoreSubscriptions(options) {
|
|
1298
|
+
if (!options?.retry) {
|
|
1299
|
+
for (const entry of this.filterSubscriptions.values()) {
|
|
1300
|
+
if (entry.synthetic) continue;
|
|
1301
|
+
this.subscriptions.delete(entry.serverId);
|
|
1302
|
+
entry.restorePending = true;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
const entriesToRestore = Array.from(this.filterSubscriptions.entries()).filter(([, entry]) => entry.restorePending && !entry.synthetic);
|
|
1306
|
+
let failureCount = 0;
|
|
1307
|
+
let lastError;
|
|
1209
1308
|
for (const [hash, entry] of entriesToRestore) {
|
|
1210
|
-
if (entry.callbacks.size === 0)
|
|
1309
|
+
if (entry.callbacks.size === 0) {
|
|
1310
|
+
entry.restorePending = false;
|
|
1311
|
+
continue;
|
|
1312
|
+
}
|
|
1211
1313
|
try {
|
|
1212
1314
|
const payload = { filters: entry.filters };
|
|
1213
1315
|
const response = await this.sendRequest(
|
|
@@ -1215,22 +1317,24 @@ var EventHandler = class {
|
|
|
1215
1317
|
"subscribe" /* Subscribe */
|
|
1216
1318
|
);
|
|
1217
1319
|
const newServerId = response.subscription_id;
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
filters: entry.filters,
|
|
1221
|
-
callbacks: entry.callbacks
|
|
1222
|
-
});
|
|
1320
|
+
entry.serverId = newServerId;
|
|
1321
|
+
entry.restorePending = false;
|
|
1223
1322
|
this.subscriptions.set(newServerId, hash);
|
|
1224
1323
|
for (const callback of entry.callbacks) {
|
|
1225
1324
|
this.callbackToHash.set(callback, hash);
|
|
1226
1325
|
}
|
|
1227
1326
|
} catch (error) {
|
|
1228
1327
|
logger.error("Failed to restore subscription:", error);
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
}
|
|
1328
|
+
failureCount++;
|
|
1329
|
+
lastError = error;
|
|
1232
1330
|
}
|
|
1233
1331
|
}
|
|
1332
|
+
if (failureCount > 0) {
|
|
1333
|
+
const message = lastError instanceof Error ? lastError.message : String(lastError);
|
|
1334
|
+
throw new Error(
|
|
1335
|
+
`Failed to restore ${failureCount} subscription(s) after reconnect: ${message}`
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1234
1338
|
}
|
|
1235
1339
|
};
|
|
1236
1340
|
var EventSubscriptions = class {
|
|
@@ -1265,7 +1369,15 @@ var EventSubscriptions = class {
|
|
|
1265
1369
|
/**
|
|
1266
1370
|
* Subscribe to events for a specific path
|
|
1267
1371
|
*
|
|
1268
|
-
*
|
|
1372
|
+
* Path pattern semantics (matched server-side):
|
|
1373
|
+
* - A plain path matches **only that exact node** — there is no implicit
|
|
1374
|
+
* prefix matching, so `/folder` does NOT match `/folder/child`.
|
|
1375
|
+
* - `*` matches exactly one path segment: `/folder/*` matches
|
|
1376
|
+
* `/folder/child` but not `/folder/child/grandchild`.
|
|
1377
|
+
* - `**` matches recursively: `/folder/**` matches every descendant of
|
|
1378
|
+
* `/folder` (but not `/folder` itself).
|
|
1379
|
+
*
|
|
1380
|
+
* @param path - Path pattern (e.g. "/folder", "/folder/*", "/folder/**")
|
|
1269
1381
|
* @param callback - Callback function for events
|
|
1270
1382
|
* @param options - Optional subscription options
|
|
1271
1383
|
* @param options.includeNode - Include full node data in event payload (default: false)
|
|
@@ -1312,6 +1424,40 @@ var NodeOperations = class {
|
|
|
1312
1424
|
const result = await this.sendRequest(payload);
|
|
1313
1425
|
return result;
|
|
1314
1426
|
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Create a node, auto-creating any missing ancestor folders.
|
|
1429
|
+
*
|
|
1430
|
+
* @param options - Node creation options (with optional `parentNodeType`)
|
|
1431
|
+
* @returns Created node
|
|
1432
|
+
*/
|
|
1433
|
+
async createDeep(options) {
|
|
1434
|
+
const payload = {
|
|
1435
|
+
node_type: options.type,
|
|
1436
|
+
path: options.path,
|
|
1437
|
+
properties: options.properties ?? {},
|
|
1438
|
+
content: options.content,
|
|
1439
|
+
parent_node_type: options.parentNodeType
|
|
1440
|
+
};
|
|
1441
|
+
const result = await this.sendRequest(payload, "node_create_deep");
|
|
1442
|
+
return result;
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Upsert a node by path (create-or-update), auto-creating any missing ancestor folders.
|
|
1446
|
+
*
|
|
1447
|
+
* @param options - Node creation options (with optional `parentNodeType`)
|
|
1448
|
+
* @returns Upserted node
|
|
1449
|
+
*/
|
|
1450
|
+
async upsertDeep(options) {
|
|
1451
|
+
const payload = {
|
|
1452
|
+
node_type: options.type,
|
|
1453
|
+
path: options.path,
|
|
1454
|
+
properties: options.properties ?? {},
|
|
1455
|
+
content: options.content,
|
|
1456
|
+
parent_node_type: options.parentNodeType
|
|
1457
|
+
};
|
|
1458
|
+
const result = await this.sendRequest(payload, "node_upsert_deep");
|
|
1459
|
+
return result;
|
|
1460
|
+
}
|
|
1315
1461
|
/**
|
|
1316
1462
|
* Update an existing node
|
|
1317
1463
|
*
|
|
@@ -1361,6 +1507,96 @@ var NodeOperations = class {
|
|
|
1361
1507
|
throw error;
|
|
1362
1508
|
}
|
|
1363
1509
|
}
|
|
1510
|
+
/**
|
|
1511
|
+
* List a node's revision history (git-style "file history"), newest first.
|
|
1512
|
+
*
|
|
1513
|
+
* Each entry carries the `revision` (usable with `atRevision()` reads to
|
|
1514
|
+
* fetch the full snapshot), plus when/who and whether the node was deleted
|
|
1515
|
+
* at that revision. Always available regardless of the NodeType `auditable`
|
|
1516
|
+
* flag — it reflects the structural MVCC version history.
|
|
1517
|
+
*
|
|
1518
|
+
* @param id - Node ID
|
|
1519
|
+
* @param options - Optional `{ limit }` to cap the number of revisions
|
|
1520
|
+
* @returns Array of revision entries (newest first)
|
|
1521
|
+
*
|
|
1522
|
+
* @example
|
|
1523
|
+
* ```typescript
|
|
1524
|
+
* const history = await ws.nodes().history(nodeId, { limit: 50 });
|
|
1525
|
+
* for (const rev of history) {
|
|
1526
|
+
* const snapshot = await ws.atRevision(rev.revision).nodes().get(nodeId);
|
|
1527
|
+
* }
|
|
1528
|
+
* ```
|
|
1529
|
+
*/
|
|
1530
|
+
async history(id, options) {
|
|
1531
|
+
const payload = {
|
|
1532
|
+
node_id: id,
|
|
1533
|
+
limit: options?.limit
|
|
1534
|
+
};
|
|
1535
|
+
const result = await this.sendRequest(payload, "node_history");
|
|
1536
|
+
return result ?? [];
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Get a node by its localized URL path (plan Phase 12).
|
|
1540
|
+
*
|
|
1541
|
+
* @param locale - Locale code (`fr`, `fr-CA`)
|
|
1542
|
+
* @param path - The node's path in that locale (`/produits/chaise`)
|
|
1543
|
+
* @returns The translated node with `canonical_path`,
|
|
1544
|
+
* `canonical_localized_path`, hreflang `alternates` and a `redirect`
|
|
1545
|
+
* hint, or `null` when nothing resolves (missing, hidden in the locale
|
|
1546
|
+
* or not readable — the same answer)
|
|
1547
|
+
*
|
|
1548
|
+
* @example
|
|
1549
|
+
* ```typescript
|
|
1550
|
+
* const hit = await ws.nodes().getByLocalizedPath('fr', '/produits/chaise');
|
|
1551
|
+
* if (hit?.redirect) redirect301(`/fr${hit.canonical_localized_path}`);
|
|
1552
|
+
* ```
|
|
1553
|
+
*/
|
|
1554
|
+
async getByLocalizedPath(locale, path) {
|
|
1555
|
+
const payload = { locale, path };
|
|
1556
|
+
const result = await this.sendRequest(payload, "node_get_by_localized_path");
|
|
1557
|
+
return result ?? null;
|
|
1558
|
+
}
|
|
1559
|
+
/**
|
|
1560
|
+
* List a node's revision history by path (newest first).
|
|
1561
|
+
*
|
|
1562
|
+
* @param path - Node path
|
|
1563
|
+
* @param options - Optional `{ limit }` to cap the number of revisions
|
|
1564
|
+
* @returns Array of revision entries (newest first)
|
|
1565
|
+
*/
|
|
1566
|
+
async historyByPath(path, options) {
|
|
1567
|
+
const payload = {
|
|
1568
|
+
path,
|
|
1569
|
+
limit: options?.limit
|
|
1570
|
+
};
|
|
1571
|
+
const result = await this.sendRequest(payload, "node_history");
|
|
1572
|
+
return result ?? [];
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* Query a node's audit-log entries by ID.
|
|
1576
|
+
*
|
|
1577
|
+
* Audit logs are only produced for NodeTypes marked `auditable`. This is
|
|
1578
|
+
* distinct from {@link history}, which is the always-on structural revision
|
|
1579
|
+
* history available for every node.
|
|
1580
|
+
*
|
|
1581
|
+
* @param id - Node ID
|
|
1582
|
+
* @returns Array of audit-log entries
|
|
1583
|
+
*/
|
|
1584
|
+
async auditLog(id) {
|
|
1585
|
+
const payload = { node_id: id };
|
|
1586
|
+
const result = await this.sendRequest(payload, "audit_query");
|
|
1587
|
+
return result ?? [];
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Query a node's audit-log entries by path.
|
|
1591
|
+
*
|
|
1592
|
+
* @param path - Node path
|
|
1593
|
+
* @returns Array of audit-log entries
|
|
1594
|
+
*/
|
|
1595
|
+
async auditLogByPath(path) {
|
|
1596
|
+
const payload = { path };
|
|
1597
|
+
const result = await this.sendRequest(payload, "audit_query");
|
|
1598
|
+
return result ?? [];
|
|
1599
|
+
}
|
|
1364
1600
|
/**
|
|
1365
1601
|
* Query nodes
|
|
1366
1602
|
*
|
|
@@ -1383,15 +1619,8 @@ var NodeOperations = class {
|
|
|
1383
1619
|
* @returns Node or null if not found
|
|
1384
1620
|
*/
|
|
1385
1621
|
async getByPath(path) {
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
query: { path },
|
|
1389
|
-
limit: 1
|
|
1390
|
-
});
|
|
1391
|
-
return result.length > 0 ? result[0] : null;
|
|
1392
|
-
} catch (error) {
|
|
1393
|
-
return null;
|
|
1394
|
-
}
|
|
1622
|
+
const result = await this.sendRequest({ path }, "node_query_by_path");
|
|
1623
|
+
return result ?? null;
|
|
1395
1624
|
}
|
|
1396
1625
|
/**
|
|
1397
1626
|
* Query nodes by property
|
|
@@ -1402,14 +1631,12 @@ var NodeOperations = class {
|
|
|
1402
1631
|
* @returns Array of nodes
|
|
1403
1632
|
*/
|
|
1404
1633
|
async queryByProperty(propertyName, propertyValue, limit) {
|
|
1405
|
-
|
|
1406
|
-
query: {
|
|
1407
|
-
properties: {
|
|
1408
|
-
[propertyName]: propertyValue
|
|
1409
|
-
}
|
|
1410
|
-
},
|
|
1634
|
+
const payload = {
|
|
1635
|
+
query: { [propertyName]: propertyValue },
|
|
1411
1636
|
limit
|
|
1412
|
-
}
|
|
1637
|
+
};
|
|
1638
|
+
const result = await this.sendRequest(payload, "node_query_by_property");
|
|
1639
|
+
return result;
|
|
1413
1640
|
}
|
|
1414
1641
|
/**
|
|
1415
1642
|
* Query nodes by type
|
|
@@ -1420,7 +1647,7 @@ var NodeOperations = class {
|
|
|
1420
1647
|
*/
|
|
1421
1648
|
async queryByType(nodeType, limit) {
|
|
1422
1649
|
return this.query({
|
|
1423
|
-
query: {
|
|
1650
|
+
query: { type: nodeType },
|
|
1424
1651
|
limit
|
|
1425
1652
|
});
|
|
1426
1653
|
}
|
|
@@ -1455,17 +1682,40 @@ var NodeOperations = class {
|
|
|
1455
1682
|
// Tree Operations
|
|
1456
1683
|
// ========================================================================
|
|
1457
1684
|
/**
|
|
1458
|
-
* List children of a parent node
|
|
1685
|
+
* List children of a parent node, in editorial (drag-and-drop) order.
|
|
1459
1686
|
*
|
|
1460
1687
|
* @param parentPath - Path of the parent node
|
|
1461
1688
|
* @returns Array of child nodes
|
|
1462
1689
|
*/
|
|
1463
1690
|
async listChildren(parentPath) {
|
|
1691
|
+
const { items } = await this.listChildrenPage(parentPath);
|
|
1692
|
+
return items;
|
|
1693
|
+
}
|
|
1694
|
+
/**
|
|
1695
|
+
* List one page of a parent's children, in editorial order.
|
|
1696
|
+
*
|
|
1697
|
+
* Pagination is keyset-based: pass the previous response's `nextCursor` back as
|
|
1698
|
+
* `cursor` to get the following page. `nextCursor` is `null` on the last page.
|
|
1699
|
+
* Treat the cursor as opaque — do not parse or construct it.
|
|
1700
|
+
*
|
|
1701
|
+
* @param parentPath - Path of the parent node
|
|
1702
|
+
* @param options - `cursor` from a previous page, and page `limit`
|
|
1703
|
+
*/
|
|
1704
|
+
async listChildrenPage(parentPath, options = {}) {
|
|
1464
1705
|
const payload = {
|
|
1465
1706
|
parent_path: parentPath
|
|
1466
1707
|
};
|
|
1708
|
+
if (options.cursor !== void 0) payload.cursor = options.cursor;
|
|
1709
|
+
if (options.limit !== void 0) payload.limit = options.limit;
|
|
1467
1710
|
const result = await this.sendRequest(payload, "node_list_children");
|
|
1468
|
-
|
|
1711
|
+
if (Array.isArray(result)) {
|
|
1712
|
+
return { items: result, nextCursor: null };
|
|
1713
|
+
}
|
|
1714
|
+
const page = result;
|
|
1715
|
+
return {
|
|
1716
|
+
items: page.nodes ?? [],
|
|
1717
|
+
nextCursor: page.next_cursor == null ? null : JSON.stringify(page.next_cursor)
|
|
1718
|
+
};
|
|
1469
1719
|
}
|
|
1470
1720
|
/**
|
|
1471
1721
|
* Get a node tree starting from a root node
|
|
@@ -1476,7 +1726,7 @@ var NodeOperations = class {
|
|
|
1476
1726
|
*/
|
|
1477
1727
|
async getTree(rootPath, maxDepth) {
|
|
1478
1728
|
const payload = {
|
|
1479
|
-
|
|
1729
|
+
parent_path: rootPath
|
|
1480
1730
|
};
|
|
1481
1731
|
if (maxDepth !== void 0) {
|
|
1482
1732
|
payload.max_depth = maxDepth;
|
|
@@ -1493,7 +1743,7 @@ var NodeOperations = class {
|
|
|
1493
1743
|
*/
|
|
1494
1744
|
async getTreeFlat(rootPath, maxDepth) {
|
|
1495
1745
|
const payload = {
|
|
1496
|
-
|
|
1746
|
+
parent_path: rootPath
|
|
1497
1747
|
};
|
|
1498
1748
|
if (maxDepth !== void 0) {
|
|
1499
1749
|
payload.max_depth = maxDepth;
|
|
@@ -1528,7 +1778,7 @@ var NodeOperations = class {
|
|
|
1528
1778
|
*/
|
|
1529
1779
|
async rename(nodePath, newName) {
|
|
1530
1780
|
const payload = {
|
|
1531
|
-
|
|
1781
|
+
old_path: nodePath,
|
|
1532
1782
|
new_name: newName
|
|
1533
1783
|
};
|
|
1534
1784
|
const result = await this.sendRequest(payload, "node_rename");
|
|
@@ -1544,9 +1794,8 @@ var NodeOperations = class {
|
|
|
1544
1794
|
*/
|
|
1545
1795
|
async copy(fromPath, toParentPath, newName) {
|
|
1546
1796
|
const payload = {
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
deep: false
|
|
1797
|
+
source_path: fromPath,
|
|
1798
|
+
target_parent: toParentPath
|
|
1550
1799
|
};
|
|
1551
1800
|
if (newName !== void 0) {
|
|
1552
1801
|
payload.new_name = newName;
|
|
@@ -1564,9 +1813,8 @@ var NodeOperations = class {
|
|
|
1564
1813
|
*/
|
|
1565
1814
|
async copyTree(fromPath, toParentPath, newName) {
|
|
1566
1815
|
const payload = {
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
deep: true
|
|
1816
|
+
source_path: fromPath,
|
|
1817
|
+
target_parent: toParentPath
|
|
1570
1818
|
};
|
|
1571
1819
|
if (newName !== void 0) {
|
|
1572
1820
|
payload.new_name = newName;
|
|
@@ -1575,53 +1823,72 @@ var NodeOperations = class {
|
|
|
1575
1823
|
return result;
|
|
1576
1824
|
}
|
|
1577
1825
|
/**
|
|
1578
|
-
*
|
|
1826
|
+
* Move a child to a specific position among its siblings.
|
|
1579
1827
|
*
|
|
1580
|
-
*
|
|
1581
|
-
*
|
|
1582
|
-
*
|
|
1828
|
+
* Positions are 0-based; a position past the end appends. The server owns the
|
|
1829
|
+
* fractional order key — you name a position, it mints the key.
|
|
1830
|
+
*
|
|
1831
|
+
* @param parentPath - Parent node path
|
|
1832
|
+
* @param childName - Name of the child to move (not a full path)
|
|
1833
|
+
* @param position - Target 0-based position among the parent's children
|
|
1834
|
+
* @returns The reordered node, carrying its newly assigned `order_key`
|
|
1583
1835
|
*/
|
|
1584
|
-
async reorder(
|
|
1836
|
+
async reorder(parentPath, childName, position) {
|
|
1585
1837
|
const payload = {
|
|
1586
|
-
|
|
1587
|
-
|
|
1838
|
+
parent_path: parentPath,
|
|
1839
|
+
child_name: childName,
|
|
1840
|
+
position
|
|
1588
1841
|
};
|
|
1589
1842
|
const result = await this.sendRequest(payload, "node_reorder");
|
|
1590
1843
|
return result;
|
|
1591
1844
|
}
|
|
1592
1845
|
/**
|
|
1593
|
-
* Move a child
|
|
1846
|
+
* Move a child so it sits immediately before one of its siblings.
|
|
1594
1847
|
*
|
|
1595
1848
|
* @param parentPath - Parent node path
|
|
1596
|
-
* @param
|
|
1597
|
-
* @param
|
|
1598
|
-
* @returns Moved node
|
|
1849
|
+
* @param childName - Name of the child to move (not a full path)
|
|
1850
|
+
* @param beforeChildName - Name of the sibling to sit before
|
|
1599
1851
|
*/
|
|
1600
|
-
async moveChildBefore(parentPath,
|
|
1852
|
+
async moveChildBefore(parentPath, childName, beforeChildName) {
|
|
1601
1853
|
const payload = {
|
|
1602
1854
|
parent_path: parentPath,
|
|
1603
|
-
|
|
1604
|
-
|
|
1855
|
+
child_name: childName,
|
|
1856
|
+
before_child_name: beforeChildName
|
|
1605
1857
|
};
|
|
1606
|
-
|
|
1607
|
-
return result;
|
|
1858
|
+
await this.sendRequest(payload, "node_move_child_before");
|
|
1608
1859
|
}
|
|
1609
1860
|
/**
|
|
1610
|
-
* Move a child
|
|
1861
|
+
* Move a child so it sits immediately after one of its siblings.
|
|
1611
1862
|
*
|
|
1612
1863
|
* @param parentPath - Parent node path
|
|
1613
|
-
* @param
|
|
1614
|
-
* @param
|
|
1615
|
-
* @returns Moved node
|
|
1864
|
+
* @param childName - Name of the child to move (not a full path)
|
|
1865
|
+
* @param afterChildName - Name of the sibling to sit after
|
|
1616
1866
|
*/
|
|
1617
|
-
async moveChildAfter(parentPath,
|
|
1867
|
+
async moveChildAfter(parentPath, childName, afterChildName) {
|
|
1618
1868
|
const payload = {
|
|
1619
1869
|
parent_path: parentPath,
|
|
1620
|
-
|
|
1621
|
-
|
|
1870
|
+
child_name: childName,
|
|
1871
|
+
after_child_name: afterChildName
|
|
1622
1872
|
};
|
|
1623
|
-
|
|
1624
|
-
|
|
1873
|
+
await this.sendRequest(payload, "node_move_child_after");
|
|
1874
|
+
}
|
|
1875
|
+
/**
|
|
1876
|
+
* Replay a parent's child order from another branch onto the current branch.
|
|
1877
|
+
*
|
|
1878
|
+
* Carries node sibling-order across branches — useful for selective publish
|
|
1879
|
+
* flows, where copying node content (e.g. via SQL) does not move the ordering
|
|
1880
|
+
* index. Only children that exist under the parent on both branches are
|
|
1881
|
+
* reordered. The target branch is the connection's current branch.
|
|
1882
|
+
*
|
|
1883
|
+
* @param parentPath - Parent node path
|
|
1884
|
+
* @param sourceBranch - Branch to copy the child order FROM
|
|
1885
|
+
*/
|
|
1886
|
+
async applyChildOrder(parentPath, sourceBranch) {
|
|
1887
|
+
const payload = {
|
|
1888
|
+
parent_path: parentPath,
|
|
1889
|
+
source_branch: sourceBranch
|
|
1890
|
+
};
|
|
1891
|
+
await this.sendRequest(payload, "node_apply_child_order");
|
|
1625
1892
|
}
|
|
1626
1893
|
// ========================================================================
|
|
1627
1894
|
// Relationship Operations
|
|
@@ -1897,7 +2164,17 @@ var NodeFileSource = class {
|
|
|
1897
2164
|
return;
|
|
1898
2165
|
}
|
|
1899
2166
|
if (!this._fsPromises) {
|
|
1900
|
-
|
|
2167
|
+
if (!isNodeEnvironment()) {
|
|
2168
|
+
throw new Error(
|
|
2169
|
+
"NodeFileSource requires Node.js (fs/promises is not available in browsers). In the browser, use a File or Blob object instead."
|
|
2170
|
+
);
|
|
2171
|
+
}
|
|
2172
|
+
const specifier = "fs/promises";
|
|
2173
|
+
this._fsPromises = await import(
|
|
2174
|
+
/* @vite-ignore */
|
|
2175
|
+
/* webpackIgnore: true */
|
|
2176
|
+
specifier
|
|
2177
|
+
);
|
|
1901
2178
|
}
|
|
1902
2179
|
const stats = await this._fsPromises.stat(this._filePath);
|
|
1903
2180
|
this._size = stats.size;
|
|
@@ -2506,7 +2783,8 @@ function createSqlHandler(sendRequest) {
|
|
|
2506
2783
|
|
|
2507
2784
|
// src/node-types.ts
|
|
2508
2785
|
var NodeTypes = class {
|
|
2509
|
-
constructor(
|
|
2786
|
+
constructor(context, sendRequest) {
|
|
2787
|
+
this.context = context;
|
|
2510
2788
|
this.sendRequest = sendRequest;
|
|
2511
2789
|
}
|
|
2512
2790
|
/**
|
|
@@ -2590,14 +2868,25 @@ var NodeTypes = class {
|
|
|
2590
2868
|
);
|
|
2591
2869
|
}
|
|
2592
2870
|
/**
|
|
2593
|
-
* Validate a node against its NodeType
|
|
2871
|
+
* Validate a node against its NodeType.
|
|
2872
|
+
*
|
|
2873
|
+
* The server validates in the context of a workspace (workspace-level
|
|
2874
|
+
* allow-lists and unique checks), so one is required. Pass it here, or
|
|
2875
|
+
* create the database with a workspace already in its context.
|
|
2594
2876
|
*/
|
|
2595
|
-
async validate(node) {
|
|
2877
|
+
async validate(node, workspace) {
|
|
2878
|
+
const ws = workspace ?? this.context.workspace;
|
|
2879
|
+
if (!ws) {
|
|
2880
|
+
throw new Error(
|
|
2881
|
+
"nodeTypes().validate() requires a workspace: pass it as the second argument"
|
|
2882
|
+
);
|
|
2883
|
+
}
|
|
2596
2884
|
return this.sendRequest(
|
|
2597
2885
|
{
|
|
2598
2886
|
node
|
|
2599
2887
|
},
|
|
2600
|
-
"node_type_validate" /* NodeTypeValidate
|
|
2888
|
+
"node_type_validate" /* NodeTypeValidate */,
|
|
2889
|
+
{ ...this.context, workspace: ws }
|
|
2601
2890
|
);
|
|
2602
2891
|
}
|
|
2603
2892
|
/**
|
|
@@ -2698,6 +2987,17 @@ var Archetypes = class {
|
|
|
2698
2987
|
"archetype_unpublish" /* ArchetypeUnpublish */
|
|
2699
2988
|
);
|
|
2700
2989
|
}
|
|
2990
|
+
/**
|
|
2991
|
+
* Get resolved Archetype with full inheritance applied
|
|
2992
|
+
*/
|
|
2993
|
+
async getResolved(name) {
|
|
2994
|
+
return this.sendRequest(
|
|
2995
|
+
{
|
|
2996
|
+
name
|
|
2997
|
+
},
|
|
2998
|
+
"archetype_get_resolved" /* ArchetypeGetResolved */
|
|
2999
|
+
);
|
|
3000
|
+
}
|
|
2701
3001
|
};
|
|
2702
3002
|
|
|
2703
3003
|
// src/element-types.ts
|
|
@@ -2785,6 +3085,17 @@ var ElementTypes = class {
|
|
|
2785
3085
|
"element_type_unpublish" /* ElementTypeUnpublish */
|
|
2786
3086
|
);
|
|
2787
3087
|
}
|
|
3088
|
+
/**
|
|
3089
|
+
* Get resolved ElementType with full inheritance applied
|
|
3090
|
+
*/
|
|
3091
|
+
async getResolved(name) {
|
|
3092
|
+
return this.sendRequest(
|
|
3093
|
+
{
|
|
3094
|
+
name
|
|
3095
|
+
},
|
|
3096
|
+
"element_type_get_resolved" /* ElementTypeGetResolved */
|
|
3097
|
+
);
|
|
3098
|
+
}
|
|
2788
3099
|
};
|
|
2789
3100
|
|
|
2790
3101
|
// src/branches.ts
|
|
@@ -2886,6 +3197,110 @@ var Branches = class {
|
|
|
2886
3197
|
"branch_compare" /* BranchCompare */
|
|
2887
3198
|
);
|
|
2888
3199
|
}
|
|
3200
|
+
/**
|
|
3201
|
+
* Per-node diff of `branch` relative to `baseBranch` (added/modified/deleted).
|
|
3202
|
+
*
|
|
3203
|
+
* Cheaper than reading both branch listings: cost scales with the number of
|
|
3204
|
+
* changed nodes since the branches diverged, not the total content.
|
|
3205
|
+
*/
|
|
3206
|
+
async diff(branch, baseBranch) {
|
|
3207
|
+
return this.sendRequest(
|
|
3208
|
+
{
|
|
3209
|
+
branch,
|
|
3210
|
+
base_branch: baseBranch
|
|
3211
|
+
},
|
|
3212
|
+
"branch_diff" /* BranchDiff */
|
|
3213
|
+
);
|
|
3214
|
+
}
|
|
3215
|
+
/**
|
|
3216
|
+
* Copy a node set from `sourceBranch` onto `targetBranch` (branch
|
|
3217
|
+
* promotion): node ids are preserved, so repeated copies update the same
|
|
3218
|
+
* target nodes; all changes land in ONE atomic commit on the target branch.
|
|
3219
|
+
*
|
|
3220
|
+
* Each root's parent path must already exist on the target branch. With
|
|
3221
|
+
* `deleteMissing`, target nodes under the roots that are absent from the
|
|
3222
|
+
* copied source set are pruned (one-way sync).
|
|
3223
|
+
*
|
|
3224
|
+
* `sourceRevision` pins the read to a point in time. A promotion of a large
|
|
3225
|
+
* set is not instantaneous, so without it the copy reads each node when its
|
|
3226
|
+
* turn comes and a writer working in parallel lands partly inside the result
|
|
3227
|
+
* — a torn snapshot. Pass the revision captured when the operation was
|
|
3228
|
+
* decided (see `getHead`) to copy exactly that state. The target branch is
|
|
3229
|
+
* always resolved at its head: the pin says WHAT to copy, never where.
|
|
3230
|
+
*/
|
|
3231
|
+
async copyNodes(sourceBranch, targetBranch, opts) {
|
|
3232
|
+
return this.sendRequest(
|
|
3233
|
+
{
|
|
3234
|
+
source_branch: sourceBranch,
|
|
3235
|
+
target_branch: targetBranch,
|
|
3236
|
+
workspace: opts.workspace,
|
|
3237
|
+
roots: opts.roots,
|
|
3238
|
+
recursive: opts.recursive ?? true,
|
|
3239
|
+
delete_missing: opts.deleteMissing ?? false,
|
|
3240
|
+
source_revision: opts.sourceRevision ?? null
|
|
3241
|
+
},
|
|
3242
|
+
"branch_copy_nodes" /* BranchCopyNodes */
|
|
3243
|
+
);
|
|
3244
|
+
}
|
|
3245
|
+
};
|
|
3246
|
+
|
|
3247
|
+
// src/scheduler.ts
|
|
3248
|
+
var SchedulerApi = class {
|
|
3249
|
+
constructor(_context, sendRequest) {
|
|
3250
|
+
this.sendRequest = sendRequest;
|
|
3251
|
+
}
|
|
3252
|
+
/**
|
|
3253
|
+
* Schedule a one-shot invocation of a function or flow at a fixed time.
|
|
3254
|
+
*/
|
|
3255
|
+
async schedule(options) {
|
|
3256
|
+
const runAt = options.runAt instanceof Date ? options.runAt.toISOString() : options.runAt;
|
|
3257
|
+
return this.sendRequest(
|
|
3258
|
+
{
|
|
3259
|
+
target_kind: options.targetKind,
|
|
3260
|
+
target_path: options.targetPath,
|
|
3261
|
+
input: options.input ?? {},
|
|
3262
|
+
run_at: runAt,
|
|
3263
|
+
external_key: options.externalKey,
|
|
3264
|
+
branch: options.branch,
|
|
3265
|
+
workspace: options.workspace,
|
|
3266
|
+
max_retries: options.maxRetries
|
|
3267
|
+
},
|
|
3268
|
+
"scheduled_invocation_create" /* ScheduledInvocationCreate */
|
|
3269
|
+
);
|
|
3270
|
+
}
|
|
3271
|
+
/**
|
|
3272
|
+
* Cancel a pending invocation before it fires, by job id or external key.
|
|
3273
|
+
*/
|
|
3274
|
+
async cancel(ref) {
|
|
3275
|
+
const payload = typeof ref === "string" ? { job_id: ref } : { job_id: ref.jobId, external_key: ref.externalKey };
|
|
3276
|
+
return this.sendRequest(
|
|
3277
|
+
payload,
|
|
3278
|
+
"scheduled_invocation_cancel" /* ScheduledInvocationCancel */
|
|
3279
|
+
);
|
|
3280
|
+
}
|
|
3281
|
+
/**
|
|
3282
|
+
* List this repository's scheduled invocations.
|
|
3283
|
+
*/
|
|
3284
|
+
async list(filter) {
|
|
3285
|
+
const result = await this.sendRequest(
|
|
3286
|
+
{
|
|
3287
|
+
external_key: filter?.externalKey,
|
|
3288
|
+
status: filter?.status
|
|
3289
|
+
},
|
|
3290
|
+
"scheduled_invocation_list" /* ScheduledInvocationList */
|
|
3291
|
+
);
|
|
3292
|
+
return result?.invocations ?? [];
|
|
3293
|
+
}
|
|
3294
|
+
/**
|
|
3295
|
+
* Fetch a single scheduled invocation, by job id or external key.
|
|
3296
|
+
*/
|
|
3297
|
+
async get(ref) {
|
|
3298
|
+
const payload = typeof ref === "string" ? { job_id: ref } : { job_id: ref.jobId, external_key: ref.externalKey };
|
|
3299
|
+
return this.sendRequest(
|
|
3300
|
+
payload,
|
|
3301
|
+
"scheduled_invocation_get" /* ScheduledInvocationGet */
|
|
3302
|
+
);
|
|
3303
|
+
}
|
|
2889
3304
|
};
|
|
2890
3305
|
|
|
2891
3306
|
// src/tags.ts
|
|
@@ -3064,29 +3479,440 @@ function createAsyncQueue() {
|
|
|
3064
3479
|
};
|
|
3065
3480
|
}
|
|
3066
3481
|
|
|
3067
|
-
// src/
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3482
|
+
// src/agent-run-children.ts
|
|
3483
|
+
function spawnBody(o) {
|
|
3484
|
+
return {
|
|
3485
|
+
objective: o.objective,
|
|
3486
|
+
budgets: o.budgets,
|
|
3487
|
+
on_exceeded: o.onExceeded,
|
|
3488
|
+
spawn_key: o.spawnKey,
|
|
3489
|
+
subject: o.subject,
|
|
3490
|
+
as_agent: o.asAgent,
|
|
3491
|
+
agent_ref: o.agentRef,
|
|
3492
|
+
input: o.input ?? null,
|
|
3493
|
+
executor_config: o.executorConfig,
|
|
3494
|
+
reducer: o.reducer,
|
|
3495
|
+
inherit_reducer: o.inheritReducer ?? false
|
|
3496
|
+
};
|
|
3497
|
+
}
|
|
3498
|
+
function checkpointBody(o) {
|
|
3499
|
+
return {
|
|
3500
|
+
fence: o.fence,
|
|
3501
|
+
operation_id: o.operationId,
|
|
3502
|
+
reason: o.reason,
|
|
3503
|
+
summary: o.summary,
|
|
3504
|
+
transcript_cutoff: o.transcriptCutoff,
|
|
3505
|
+
state: o.state,
|
|
3506
|
+
large_refs: o.largeRefs ?? []
|
|
3507
|
+
};
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
// src/agent-runs.ts
|
|
3511
|
+
function createRunBody(options) {
|
|
3512
|
+
return {
|
|
3513
|
+
subject: options.subject,
|
|
3514
|
+
branch: options.branch,
|
|
3515
|
+
agent_ref: options.agentRef,
|
|
3516
|
+
create_key: options.createKey,
|
|
3517
|
+
budgets: options.budgets,
|
|
3518
|
+
input: options.input ?? null,
|
|
3519
|
+
reducer: options.reducer,
|
|
3520
|
+
executor_config: options.executorConfig,
|
|
3521
|
+
control_capability: options.controlCapability,
|
|
3522
|
+
as_agent: options.asAgent
|
|
3523
|
+
};
|
|
3524
|
+
}
|
|
3525
|
+
function subjectOf(subject) {
|
|
3526
|
+
if (typeof subject !== "string") return subject;
|
|
3527
|
+
const cut = subject.indexOf(":/");
|
|
3528
|
+
if (cut <= 0) throw new Error(`subject must be "workspace:/path", got "${subject}"`);
|
|
3529
|
+
return { workspace: subject.slice(0, cut), path: subject.slice(cut + 1) };
|
|
3530
|
+
}
|
|
3531
|
+
var controlCounter = 0;
|
|
3532
|
+
function newControlId(prefix = "ctl") {
|
|
3533
|
+
controlCounter += 1;
|
|
3534
|
+
const rand = Math.random().toString(36).slice(2, 10);
|
|
3535
|
+
return `${prefix}-${Date.now().toString(36)}-${controlCounter}-${rand}`;
|
|
3536
|
+
}
|
|
3537
|
+
var AgentRunsApi = class {
|
|
3538
|
+
constructor(repository, transport) {
|
|
3539
|
+
this.repository = repository;
|
|
3540
|
+
this.transport = transport;
|
|
3541
|
+
}
|
|
3542
|
+
base(runId, rest = "") {
|
|
3543
|
+
const repo = encodeURIComponent(this.repository);
|
|
3544
|
+
const run = runId ? `/${encodeURIComponent(runId)}` : "";
|
|
3545
|
+
return `/api/agent-runs/${repo}${run}${rest}`;
|
|
3546
|
+
}
|
|
3547
|
+
/** Create a run, or get back the subject's live run. */
|
|
3548
|
+
create(options) {
|
|
3549
|
+
return this.transport.request("POST", this.base(), createRunBody(options));
|
|
3550
|
+
}
|
|
3551
|
+
/** Every run about `subject` you may see, newest (the live one) first. */
|
|
3552
|
+
bySubject(subject, limit = 20) {
|
|
3553
|
+
const s = subjectOf(subject);
|
|
3554
|
+
const q = new URLSearchParams({ subject: `${s.workspace}:${s.path ?? ""}`, limit: String(limit) });
|
|
3555
|
+
if (s.node_id) q.set("subject_node_id", s.node_id);
|
|
3556
|
+
return this.transport.request("GET", `${this.base()}?${q.toString()}`);
|
|
3557
|
+
}
|
|
3558
|
+
/** Read a run (record + effective projection). */
|
|
3559
|
+
get(runId) {
|
|
3560
|
+
return this.transport.request("GET", this.base(runId));
|
|
3561
|
+
}
|
|
3562
|
+
/** Your runs in one status (default "running"). */
|
|
3563
|
+
list(status = "running", limit = 50) {
|
|
3564
|
+
return this.transport.request("GET", `${this.base()}?status=${status}&limit=${limit}`);
|
|
3565
|
+
}
|
|
3566
|
+
/** Durable events after `afterSeq` (gap-free; page with the last seq). */
|
|
3567
|
+
events(runId, afterSeq = 0, limit = 500) {
|
|
3568
|
+
return this.transport.request("GET", this.base(runId, `/events?after_seq=${afterSeq}&limit=${limit}`));
|
|
3569
|
+
}
|
|
3570
|
+
/** Any control. Retrying with the same `controlId` is a no-op. */
|
|
3571
|
+
control(runId, command, controlId = newControlId(command.command), capability) {
|
|
3572
|
+
return this.transport.request("POST", this.base(runId, "/control"), {
|
|
3573
|
+
control_id: controlId,
|
|
3574
|
+
command,
|
|
3575
|
+
capability
|
|
3576
|
+
});
|
|
3577
|
+
}
|
|
3578
|
+
stop(runId, reason, controlId) {
|
|
3579
|
+
return this.control(runId, { command: "stop", reason }, controlId);
|
|
3580
|
+
}
|
|
3581
|
+
pause(runId, controlId) {
|
|
3582
|
+
return this.control(runId, { command: "pause" }, controlId);
|
|
3583
|
+
}
|
|
3584
|
+
resume(runId, controlId) {
|
|
3585
|
+
return this.control(runId, { command: "resume" }, controlId);
|
|
3586
|
+
}
|
|
3587
|
+
/** Queue input for the run's next safe boundary. */
|
|
3588
|
+
steer(runId, input, controlId) {
|
|
3589
|
+
return this.control(runId, { command: "steer", input }, controlId);
|
|
3590
|
+
}
|
|
3591
|
+
/** Decide an approval request; the digest ties it to one changeset. */
|
|
3592
|
+
approve(runId, requestId, subjectDigest, reject, controlId) {
|
|
3593
|
+
const decision = reject ? { decision: "reject", reason: reject.reason } : { decision: "approve" };
|
|
3594
|
+
return this.control(
|
|
3595
|
+
runId,
|
|
3596
|
+
{ command: "approve", request_id: requestId, decision, subject_digest: subjectDigest },
|
|
3597
|
+
controlId
|
|
3598
|
+
);
|
|
3599
|
+
}
|
|
3600
|
+
/** Answer an input request. */
|
|
3601
|
+
answer(runId, requestId, value, controlId) {
|
|
3602
|
+
return this.control(runId, { command: "provide_input", request_id: requestId, value }, controlId);
|
|
3072
3603
|
}
|
|
3073
3604
|
/**
|
|
3074
|
-
*
|
|
3075
|
-
*
|
|
3076
|
-
* Queues a `FunctionExecution` job and returns the execution and job IDs
|
|
3077
|
-
* immediately. The function runs asynchronously on the server.
|
|
3078
|
-
*
|
|
3079
|
-
* @param functionName - Name of the function to invoke
|
|
3080
|
-
* @param input - Input data passed to the function
|
|
3081
|
-
* @returns Execution ID and job ID for tracking
|
|
3605
|
+
* Stream the run's events: replay after `afterSeq`, then live. Ends after
|
|
3606
|
+
* the run's terminal event. Resume after a disconnect with the last `seq`.
|
|
3082
3607
|
*/
|
|
3083
|
-
async
|
|
3084
|
-
const
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3608
|
+
async *stream(runId, afterSeq = 0, signal) {
|
|
3609
|
+
const response = await this.transport.fetch(
|
|
3610
|
+
this.transport.url(this.base(runId, `/stream?after_seq=${afterSeq}`)),
|
|
3611
|
+
{ headers: { ...this.transport.headers(), Accept: "text/event-stream" }, signal }
|
|
3612
|
+
);
|
|
3613
|
+
if (!response.ok || !response.body) {
|
|
3614
|
+
throw new Error(`agent run stream failed: ${response.status} ${await response.text()}`);
|
|
3615
|
+
}
|
|
3616
|
+
const reader = response.body.getReader();
|
|
3617
|
+
const decoder = new TextDecoder();
|
|
3618
|
+
let buffer = "";
|
|
3619
|
+
for (; ; ) {
|
|
3620
|
+
const { value, done } = await reader.read();
|
|
3621
|
+
if (done) return;
|
|
3622
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3623
|
+
let cut;
|
|
3624
|
+
while ((cut = buffer.indexOf("\n\n")) >= 0) {
|
|
3625
|
+
const block = buffer.slice(0, cut);
|
|
3626
|
+
buffer = buffer.slice(cut + 2);
|
|
3627
|
+
let event = "message";
|
|
3628
|
+
let data = "";
|
|
3629
|
+
for (const line of block.split("\n")) {
|
|
3630
|
+
if (line.startsWith("event:")) event = line.slice(6).trim();
|
|
3631
|
+
else if (line.startsWith("data:")) data += line.slice(5).trim();
|
|
3632
|
+
}
|
|
3633
|
+
if (event === "end") return;
|
|
3634
|
+
if (event === "error") throw new Error(`agent run stream error: ${data}`);
|
|
3635
|
+
if (event === "run-event" && data) yield JSON.parse(data);
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
// ---- client-driven runs -------------------------------------------------
|
|
3640
|
+
/** Take the lease of a queued client-driven run. */
|
|
3641
|
+
acquire(runId) {
|
|
3642
|
+
return this.transport.request("POST", this.base(runId, "/lease/acquire"), {});
|
|
3643
|
+
}
|
|
3644
|
+
renew(runId, fence) {
|
|
3645
|
+
return this.transport.request("POST", this.base(runId, "/lease/renew"), { fence });
|
|
3646
|
+
}
|
|
3647
|
+
release(runId, fence) {
|
|
3648
|
+
return this.transport.request("POST", this.base(runId, "/lease/release"), { fence });
|
|
3649
|
+
}
|
|
3650
|
+
/** Begin an operation; returns the active operation (its `op_id`). */
|
|
3651
|
+
begin(runId, fence, op) {
|
|
3652
|
+
return this.transport.request("POST", this.base(runId, "/operations"), { fence, ...op });
|
|
3653
|
+
}
|
|
3654
|
+
/** Record an operation's result. */
|
|
3655
|
+
finish(runId, fence, opId, result) {
|
|
3656
|
+
return this.transport.request("POST", this.base(runId, "/operations/finish"), { fence, op_id: opId, ...result });
|
|
3657
|
+
}
|
|
3658
|
+
/** Open approval/input requests and wait (`kind: "approval" | "input"`). */
|
|
3659
|
+
wait(runId, fence, requests) {
|
|
3660
|
+
return this.transport.request("POST", this.base(runId, "/wait"), { fence, requests });
|
|
3661
|
+
}
|
|
3662
|
+
/** End a client-driven run. */
|
|
3663
|
+
complete(runId, fence, status, outcome) {
|
|
3664
|
+
return this.transport.request("POST", this.base(runId, "/complete"), { fence, status, outcome });
|
|
3665
|
+
}
|
|
3666
|
+
// ── Child runs, mailbox, checkpoints, usage ──────────────────────────────
|
|
3667
|
+
/** Spawn a child with a typed objective; its budgets come out of this run's. */
|
|
3668
|
+
spawnChild(runId, options) {
|
|
3669
|
+
const q = options.branch ? `?branch=${encodeURIComponent(options.branch)}` : "";
|
|
3670
|
+
return this.transport.request("POST", this.base(runId, `/children${q}`), spawnBody(options));
|
|
3671
|
+
}
|
|
3672
|
+
/** Every child of a run: the parent's link plus the child's live status. */
|
|
3673
|
+
children(runId) {
|
|
3674
|
+
return this.transport.request("GET", this.base(runId, "/children"));
|
|
3675
|
+
}
|
|
3676
|
+
/** Inspect one child: record, events after `afterSeq`, usage, checkpoint. */
|
|
3677
|
+
inspectChild(runId, childId, afterSeq = 0, limit = 200) {
|
|
3678
|
+
const rest = `/children/${encodeURIComponent(childId)}?after_seq=${afterSeq}&limit=${limit}`;
|
|
3679
|
+
return this.transport.request("GET", this.base(runId, rest));
|
|
3680
|
+
}
|
|
3681
|
+
/** Message, steer, interrupt or resume a child (idempotent per `controlId`). */
|
|
3682
|
+
controlChild(runId, childId, action, controlId = newControlId(action.action)) {
|
|
3683
|
+
const rest = `/children/${encodeURIComponent(childId)}/control`;
|
|
3684
|
+
return this.transport.request("POST", this.base(runId, rest), { control_id: controlId, ...action });
|
|
3685
|
+
}
|
|
3686
|
+
messageChild(runId, childId, message, controlId) {
|
|
3687
|
+
return this.controlChild(runId, childId, { action: "message", message }, controlId);
|
|
3688
|
+
}
|
|
3689
|
+
steerChild(runId, childId, input, controlId) {
|
|
3690
|
+
return this.controlChild(runId, childId, { action: "steer", input }, controlId);
|
|
3691
|
+
}
|
|
3692
|
+
interruptChild(runId, childId, reason, mode = "stop", controlId) {
|
|
3693
|
+
return this.controlChild(runId, childId, { action: "interrupt", mode, reason }, controlId);
|
|
3694
|
+
}
|
|
3695
|
+
/** A client-driven parent waits (under its lease) for a child's hand-back. */
|
|
3696
|
+
waitChild(runId, childId, fence, expiresAtMs) {
|
|
3697
|
+
const rest = `/children/${encodeURIComponent(childId)}/wait`;
|
|
3698
|
+
return this.transport.request("POST", this.base(runId, rest), { fence, expires_at_ms: expiresAtMs });
|
|
3699
|
+
}
|
|
3700
|
+
/** Unacknowledged mailbox items (hand-backs and child messages). */
|
|
3701
|
+
mailbox(runId) {
|
|
3702
|
+
return this.transport.request("GET", this.base(runId, "/mailbox"));
|
|
3703
|
+
}
|
|
3704
|
+
/** Acknowledge mailbox items up to `upTo`. */
|
|
3705
|
+
ackMailbox(runId, upTo) {
|
|
3706
|
+
return this.transport.request("POST", this.base(runId, "/mailbox/ack"), { up_to: upTo });
|
|
3707
|
+
}
|
|
3708
|
+
/** A child posts a message to its parent's mailbox (idempotent per id). */
|
|
3709
|
+
postToParent(runId, messageId, message) {
|
|
3710
|
+
return this.transport.request("POST", this.base(runId, "/post-to-parent"), { message_id: messageId, message });
|
|
3711
|
+
}
|
|
3712
|
+
/** Write a structured checkpoint (compaction keeps state, not prose). */
|
|
3713
|
+
checkpoint(runId, options) {
|
|
3714
|
+
return this.transport.request("POST", this.base(runId, "/checkpoints"), checkpointBody(options));
|
|
3715
|
+
}
|
|
3716
|
+
/** Read a checkpoint (default: latest) with its structured state. */
|
|
3717
|
+
readCheckpoint(runId, checkpointNo) {
|
|
3718
|
+
return this.transport.request("GET", this.base(runId, `/checkpoints/${checkpointNo ?? "latest"}`));
|
|
3719
|
+
}
|
|
3720
|
+
/** Usage accounting: own, children, reserved, spare. */
|
|
3721
|
+
usage(runId) {
|
|
3722
|
+
return this.transport.request("GET", this.base(runId, "/usage"));
|
|
3723
|
+
}
|
|
3724
|
+
};
|
|
3725
|
+
|
|
3726
|
+
// src/agent-runs-ws.ts
|
|
3727
|
+
var TERMINAL = ["completed", "failed", "stopped"];
|
|
3728
|
+
var AgentRunsWsApi = class {
|
|
3729
|
+
constructor(context, sendRequest, eventHandler) {
|
|
3730
|
+
this.context = context;
|
|
3731
|
+
this.sendRequest = sendRequest;
|
|
3732
|
+
this.eventHandler = eventHandler;
|
|
3733
|
+
}
|
|
3734
|
+
call(type, payload) {
|
|
3735
|
+
return this.sendRequest(payload, type, this.context);
|
|
3736
|
+
}
|
|
3737
|
+
/** Create a run, or get back the subject's live run. */
|
|
3738
|
+
create(options) {
|
|
3739
|
+
return this.call("agent_run_create" /* AgentRunCreate */, createRunBody(options));
|
|
3740
|
+
}
|
|
3741
|
+
/** Read a run (record + effective projection). */
|
|
3742
|
+
get(runId) {
|
|
3743
|
+
return this.call("agent_run_get" /* AgentRunGet */, { run_id: runId });
|
|
3744
|
+
}
|
|
3745
|
+
/**
|
|
3746
|
+
* Every run about `subject` (a conversation, a document) you may see,
|
|
3747
|
+
* newest first — the live run, if there is one, comes first.
|
|
3748
|
+
*/
|
|
3749
|
+
bySubject(subject, limit = 20) {
|
|
3750
|
+
return this.call("agent_run_by_subject" /* AgentRunBySubject */, { ...subjectOf(subject), limit });
|
|
3751
|
+
}
|
|
3752
|
+
/** The newest run of `subject`, or null. */
|
|
3753
|
+
async latest(subject) {
|
|
3754
|
+
const [first] = await this.bySubject(subject, 1);
|
|
3755
|
+
return first ?? null;
|
|
3756
|
+
}
|
|
3757
|
+
/** Your runs in one status (default "running"). */
|
|
3758
|
+
list(status = "running", limit = 50) {
|
|
3759
|
+
return this.call("agent_run_list" /* AgentRunList */, { status, limit });
|
|
3760
|
+
}
|
|
3761
|
+
/** Durable events after `afterSeq`. */
|
|
3762
|
+
events(runId, afterSeq = 0, limit = 500) {
|
|
3763
|
+
return this.call("agent_run_events" /* AgentRunEvents */, { run_id: runId, after_seq: afterSeq, limit });
|
|
3764
|
+
}
|
|
3765
|
+
/** The children a run spawned, with their current status. */
|
|
3766
|
+
children(runId) {
|
|
3767
|
+
return this.call("agent_run_children" /* AgentRunChildren */, { run_id: runId });
|
|
3768
|
+
}
|
|
3769
|
+
/** Any control. Retrying with the same `controlId` is a no-op. */
|
|
3770
|
+
control(runId, command, controlId = newControlId(command.command), capability) {
|
|
3771
|
+
return this.call("agent_run_control" /* AgentRunControl */, { run_id: runId, control_id: controlId, command, capability });
|
|
3772
|
+
}
|
|
3773
|
+
stop(runId, reason, controlId) {
|
|
3774
|
+
return this.control(runId, { command: "stop", reason }, controlId);
|
|
3775
|
+
}
|
|
3776
|
+
pause(runId, controlId) {
|
|
3777
|
+
return this.control(runId, { command: "pause" }, controlId);
|
|
3778
|
+
}
|
|
3779
|
+
resume(runId, controlId) {
|
|
3780
|
+
return this.control(runId, { command: "resume" }, controlId);
|
|
3781
|
+
}
|
|
3782
|
+
/** Queue input for the run's next safe boundary. */
|
|
3783
|
+
steer(runId, input, controlId) {
|
|
3784
|
+
return this.control(runId, { command: "steer", input }, controlId);
|
|
3785
|
+
}
|
|
3786
|
+
/** Decide an approval request; the digest ties it to one changeset. */
|
|
3787
|
+
approve(runId, requestId, subjectDigest, reject, controlId) {
|
|
3788
|
+
const decision = reject ? { decision: "reject", reason: reject.reason } : { decision: "approve" };
|
|
3789
|
+
return this.control(runId, { command: "approve", request_id: requestId, decision, subject_digest: subjectDigest }, controlId);
|
|
3790
|
+
}
|
|
3791
|
+
/** Answer an input request. */
|
|
3792
|
+
answer(runId, requestId, value, controlId) {
|
|
3793
|
+
return this.control(runId, { command: "provide_input", request_id: requestId, value }, controlId);
|
|
3794
|
+
}
|
|
3795
|
+
/**
|
|
3796
|
+
* Follow a run: replay after `afterSeq`, then live, until the run ends.
|
|
3797
|
+
*
|
|
3798
|
+
* Events that reach the socket before the subscription is known here are
|
|
3799
|
+
* not lost: the durable log is read once the listener is in place, live
|
|
3800
|
+
* events are held until that read is merged, and every seq is delivered
|
|
3801
|
+
* exactly once, in order.
|
|
3802
|
+
*/
|
|
3803
|
+
async subscribe(runId, options = {}) {
|
|
3804
|
+
let last = options.afterSeq ?? 0;
|
|
3805
|
+
let ready = false;
|
|
3806
|
+
let closed = false;
|
|
3807
|
+
const held = [];
|
|
3808
|
+
const buffer = [];
|
|
3809
|
+
const waiters = [];
|
|
3810
|
+
let resolveDone;
|
|
3811
|
+
const done = new Promise((r) => resolveDone = r);
|
|
3812
|
+
const close = (ended) => {
|
|
3813
|
+
if (closed) return;
|
|
3814
|
+
closed = true;
|
|
3815
|
+
if (ended) options.onEnd?.(last);
|
|
3816
|
+
for (const w of waiters.splice(0)) w({ value: void 0, done: true });
|
|
3817
|
+
resolveDone();
|
|
3818
|
+
};
|
|
3819
|
+
const deliver = (ev) => {
|
|
3820
|
+
if (closed || ev.seq <= last) return;
|
|
3821
|
+
last = ev.seq;
|
|
3822
|
+
options.onEvent?.(ev);
|
|
3823
|
+
const w = waiters.shift();
|
|
3824
|
+
if (w) w({ value: ev, done: false });
|
|
3825
|
+
else buffer.push(ev);
|
|
3826
|
+
};
|
|
3827
|
+
const listener = (message) => {
|
|
3828
|
+
if (message.event_type === "agent_run_end") {
|
|
3829
|
+
if (ready) close(true);
|
|
3830
|
+
else held.push({ seq: Number.MAX_SAFE_INTEGER });
|
|
3831
|
+
return;
|
|
3832
|
+
}
|
|
3833
|
+
const ev = message.payload;
|
|
3834
|
+
if (ready) deliver(ev);
|
|
3835
|
+
else held.push(ev);
|
|
3836
|
+
};
|
|
3837
|
+
const response = await this.call("agent_run_subscribe" /* AgentRunSubscribe */, {
|
|
3838
|
+
run_id: runId,
|
|
3839
|
+
after_seq: last
|
|
3840
|
+
});
|
|
3841
|
+
const subscriptionId = response.subscription_id;
|
|
3842
|
+
this.eventHandler.addFlowEventListener(subscriptionId, listener);
|
|
3843
|
+
const unsubscribe = async () => {
|
|
3844
|
+
this.eventHandler.removeFlowEventListener(subscriptionId);
|
|
3845
|
+
close(false);
|
|
3846
|
+
await this.call("agent_run_unsubscribe" /* AgentRunUnsubscribe */, { subscription_id: subscriptionId }).catch(() => void 0);
|
|
3847
|
+
};
|
|
3848
|
+
options.signal?.addEventListener("abort", () => void unsubscribe(), { once: true });
|
|
3849
|
+
for (; ; ) {
|
|
3850
|
+
const page = await this.events(runId, last, 500);
|
|
3851
|
+
page.forEach(deliver);
|
|
3852
|
+
if (page.length < 500) break;
|
|
3853
|
+
}
|
|
3854
|
+
ready = true;
|
|
3855
|
+
const endSeen = held.some((e) => e.seq === Number.MAX_SAFE_INTEGER);
|
|
3856
|
+
held.filter((e) => e.seq !== Number.MAX_SAFE_INTEGER).sort((a, b) => a.seq - b.seq).forEach(deliver);
|
|
3857
|
+
if (endSeen) close(true);
|
|
3858
|
+
else {
|
|
3859
|
+
const view = await this.get(runId).catch(() => null);
|
|
3860
|
+
const finalized = !view?.run?.domain || view.run.domain.finalized !== false;
|
|
3861
|
+
if (view && TERMINAL.includes(view.status) && finalized) {
|
|
3862
|
+
const tail = await this.events(runId, last, 500).catch(() => []);
|
|
3863
|
+
tail.forEach(deliver);
|
|
3864
|
+
this.eventHandler.removeFlowEventListener(subscriptionId);
|
|
3865
|
+
close(true);
|
|
3866
|
+
}
|
|
3867
|
+
}
|
|
3868
|
+
void done.then(() => this.eventHandler.removeFlowEventListener(subscriptionId));
|
|
3869
|
+
return {
|
|
3870
|
+
get lastSeq() {
|
|
3871
|
+
return last;
|
|
3872
|
+
},
|
|
3873
|
+
done,
|
|
3874
|
+
unsubscribe,
|
|
3875
|
+
[Symbol.asyncIterator]() {
|
|
3876
|
+
return {
|
|
3877
|
+
next: () => {
|
|
3878
|
+
const ev = buffer.shift();
|
|
3879
|
+
if (ev) return Promise.resolve({ value: ev, done: false });
|
|
3880
|
+
if (closed) return Promise.resolve({ value: void 0, done: true });
|
|
3881
|
+
return new Promise((r) => waiters.push(r));
|
|
3882
|
+
},
|
|
3883
|
+
return: async () => {
|
|
3884
|
+
await unsubscribe();
|
|
3885
|
+
return { value: void 0, done: true };
|
|
3886
|
+
}
|
|
3887
|
+
};
|
|
3888
|
+
}
|
|
3889
|
+
};
|
|
3890
|
+
}
|
|
3891
|
+
};
|
|
3892
|
+
|
|
3893
|
+
// src/functions-api.ts
|
|
3894
|
+
var FunctionsApi = class {
|
|
3895
|
+
constructor(repository, context, sendRequest) {
|
|
3896
|
+
this.context = { ...context, repository };
|
|
3897
|
+
this.sendRequest = sendRequest;
|
|
3898
|
+
}
|
|
3899
|
+
/**
|
|
3900
|
+
* Invoke a server-side function by name (async).
|
|
3901
|
+
*
|
|
3902
|
+
* Queues a `FunctionExecution` job and returns the execution and job IDs
|
|
3903
|
+
* immediately. The function runs asynchronously on the server.
|
|
3904
|
+
*
|
|
3905
|
+
* @param functionName - Name of the function to invoke
|
|
3906
|
+
* @param input - Input data passed to the function
|
|
3907
|
+
* @returns Execution ID and job ID for tracking
|
|
3908
|
+
*/
|
|
3909
|
+
async invoke(functionName, input, options) {
|
|
3910
|
+
const effectiveRequestTimeoutMs = options?.requestTimeoutMs;
|
|
3911
|
+
let effectiveWaitTimeoutMs = options?.waitTimeoutMs;
|
|
3912
|
+
if (options?.waitForResult) {
|
|
3913
|
+
const transportTimeoutBudget = effectiveRequestTimeoutMs ?? 3e4;
|
|
3914
|
+
effectiveWaitTimeoutMs = Math.min(
|
|
3915
|
+
effectiveWaitTimeoutMs ?? transportTimeoutBudget,
|
|
3090
3916
|
transportTimeoutBudget
|
|
3091
3917
|
);
|
|
3092
3918
|
}
|
|
@@ -3153,6 +3979,104 @@ var HttpFunctionsApi = class {
|
|
|
3153
3979
|
}
|
|
3154
3980
|
};
|
|
3155
3981
|
|
|
3982
|
+
// src/search-api.ts
|
|
3983
|
+
var ASK_FUNCTION = "ask";
|
|
3984
|
+
var DEFAULT_LIMIT = 8;
|
|
3985
|
+
var MAX_LIMIT = 50;
|
|
3986
|
+
var SearchApi = class {
|
|
3987
|
+
constructor(runSql, runFunction) {
|
|
3988
|
+
this.runSql = runSql;
|
|
3989
|
+
this.runFunction = runFunction;
|
|
3990
|
+
}
|
|
3991
|
+
/**
|
|
3992
|
+
* Find the passages that match a query, by meaning and by keyword at once.
|
|
3993
|
+
*
|
|
3994
|
+
* No model is involved, so this is the call for a search box, a "related
|
|
3995
|
+
* content" list, or anything assembling its own context.
|
|
3996
|
+
*/
|
|
3997
|
+
async search(query, options) {
|
|
3998
|
+
if (!query || !query.trim()) {
|
|
3999
|
+
throw new Error("search(query) requires a non-empty query");
|
|
4000
|
+
}
|
|
4001
|
+
if (!options || !options.workspaces || !options.workspaces.trim()) {
|
|
4002
|
+
throw new Error(
|
|
4003
|
+
"search() requires `workspaces` \u2014 name a workspace, a list, a glob, or 'ALL READABLE'. An unscoped search on a public site is how draft content reaches a visitor."
|
|
4004
|
+
);
|
|
4005
|
+
}
|
|
4006
|
+
const limit = Math.min(
|
|
4007
|
+
Math.max(Math.floor(options.limit ?? DEFAULT_LIMIT), 1),
|
|
4008
|
+
MAX_LIMIT
|
|
4009
|
+
);
|
|
4010
|
+
const distance = Number.isFinite(options.maxDistance) ? `, max_distance => ${Number(options.maxDistance)}` : "";
|
|
4011
|
+
const sql = `
|
|
4012
|
+
SELECT node_id, path, name, score, chunk_index, chunk_text, chunk_text_source
|
|
4013
|
+
FROM HYBRID_SEARCH($1, ${limit},
|
|
4014
|
+
workspaces => $2,
|
|
4015
|
+
granularity => 'chunk'${distance})
|
|
4016
|
+
`;
|
|
4017
|
+
const result = await this.runSql(sql, [query.trim(), options.workspaces.trim()]);
|
|
4018
|
+
const rows = result?.rows ?? [];
|
|
4019
|
+
return rows.map((row) => ({
|
|
4020
|
+
path: String(row.path ?? ""),
|
|
4021
|
+
nodeId: String(row.node_id ?? ""),
|
|
4022
|
+
name: String(row.name ?? ""),
|
|
4023
|
+
// `0` is a real chunk index — a document that was never split — so it
|
|
4024
|
+
// must not be collapsed into "missing" by a falsy check.
|
|
4025
|
+
chunkIndex: row.chunk_index == null ? 0 : Number(row.chunk_index),
|
|
4026
|
+
text: String(row.chunk_text ?? ""),
|
|
4027
|
+
textIsExact: row.chunk_text_source === "exact",
|
|
4028
|
+
score: Number(row.score ?? 0)
|
|
4029
|
+
})).filter((passage) => passage.text.length > 0);
|
|
4030
|
+
}
|
|
4031
|
+
/**
|
|
4032
|
+
* Answer a question from the stored content, with citations.
|
|
4033
|
+
*
|
|
4034
|
+
* Retrieves, judges whether what came back answers the question, rewrites the
|
|
4035
|
+
* query once if it does not, and answers from those passages only.
|
|
4036
|
+
*/
|
|
4037
|
+
async ask(question, options) {
|
|
4038
|
+
if (!question || !question.trim()) {
|
|
4039
|
+
throw new Error("ask(question) requires a non-empty question");
|
|
4040
|
+
}
|
|
4041
|
+
if (!options || !options.workspaces || !options.workspaces.trim()) {
|
|
4042
|
+
throw new Error(
|
|
4043
|
+
"ask() requires `workspaces` \u2014 name a workspace, a list, a glob, or 'ALL READABLE'. An answer is quoted back to whoever asked, so the corpus it may be drawn from is stated, never assumed."
|
|
4044
|
+
);
|
|
4045
|
+
}
|
|
4046
|
+
const raw = await this.runFunction(ASK_FUNCTION, {
|
|
4047
|
+
question: question.trim(),
|
|
4048
|
+
workspaces: options.workspaces.trim(),
|
|
4049
|
+
limit: options.limit,
|
|
4050
|
+
model: options.model,
|
|
4051
|
+
use_graph: options.useGraph
|
|
4052
|
+
});
|
|
4053
|
+
if (raw && typeof raw === "object" && "error" in raw && raw.error) {
|
|
4054
|
+
throw new Error(`ask failed: ${String(raw.error)}`);
|
|
4055
|
+
}
|
|
4056
|
+
const citations = Array.isArray(raw?.citations) ? raw.citations : [];
|
|
4057
|
+
return {
|
|
4058
|
+
answer: String(raw?.answer ?? ""),
|
|
4059
|
+
// Absent counts as NOT grounded. This flag is the one a caller branches
|
|
4060
|
+
// on before showing an answer, so an unexpected response shape must fail
|
|
4061
|
+
// towards "we found nothing", never towards "trust this".
|
|
4062
|
+
grounded: raw?.grounded === true,
|
|
4063
|
+
citations: citations.map((c) => ({
|
|
4064
|
+
marker: Number(c.marker ?? 0),
|
|
4065
|
+
path: String(c.path ?? ""),
|
|
4066
|
+
nodeId: String(c.node_id ?? ""),
|
|
4067
|
+
name: String(c.title ?? ""),
|
|
4068
|
+
chunkIndex: c.chunk_index == null ? 0 : Number(c.chunk_index),
|
|
4069
|
+
textIsExact: c.text_is_exact === true
|
|
4070
|
+
})),
|
|
4071
|
+
attempts: Array.isArray(raw?.attempts) ? raw.attempts.map((a) => ({
|
|
4072
|
+
query: String(a.query ?? ""),
|
|
4073
|
+
passages: Number(a.passages ?? 0)
|
|
4074
|
+
})) : [],
|
|
4075
|
+
model: String(raw?.model ?? "")
|
|
4076
|
+
};
|
|
4077
|
+
}
|
|
4078
|
+
};
|
|
4079
|
+
|
|
3156
4080
|
// src/errors.ts
|
|
3157
4081
|
var RaisinError = class extends Error {
|
|
3158
4082
|
constructor(message, code, details) {
|
|
@@ -3241,6 +4165,7 @@ var SSEClient = class {
|
|
|
3241
4165
|
this._lastEventId = options.lastEventId;
|
|
3242
4166
|
this.autoReconnect = options.reconnect?.enabled ?? true;
|
|
3243
4167
|
this.externalSignal = options.signal;
|
|
4168
|
+
this.inactivityTimeoutMs = options.inactivityTimeoutMs;
|
|
3244
4169
|
this.eventTypeSet = options.eventTypes?.length ? new Set(options.eventTypes) : null;
|
|
3245
4170
|
this.reconnectManager = new ReconnectManager({
|
|
3246
4171
|
initialDelay: options.reconnect?.initialDelay ?? 1e3,
|
|
@@ -3384,6 +4309,23 @@ var SSEClient = class {
|
|
|
3384
4309
|
if (err.name === "AbortError" || err instanceof RaisinAbortError) {
|
|
3385
4310
|
return;
|
|
3386
4311
|
}
|
|
4312
|
+
if (err instanceof RaisinTimeoutError && err.code === "SSE_INACTIVITY_TIMEOUT") {
|
|
4313
|
+
logger.warn("SSE inactivity timeout:", err.message);
|
|
4314
|
+
if (!this.autoReconnect) {
|
|
4315
|
+
this.setState("disconnected");
|
|
4316
|
+
onError?.(err);
|
|
4317
|
+
throw err;
|
|
4318
|
+
}
|
|
4319
|
+
this.setState("reconnecting");
|
|
4320
|
+
const scheduled2 = await this.waitForReconnect();
|
|
4321
|
+
if (!scheduled2) {
|
|
4322
|
+
logger.warn("SSE max reconnection attempts reached");
|
|
4323
|
+
this.setState("disconnected");
|
|
4324
|
+
onError?.(err);
|
|
4325
|
+
throw err;
|
|
4326
|
+
}
|
|
4327
|
+
continue;
|
|
4328
|
+
}
|
|
3387
4329
|
logger.error("SSE connection error:", err.message);
|
|
3388
4330
|
onError?.(err);
|
|
3389
4331
|
if (!this.autoReconnect) {
|
|
@@ -3432,7 +4374,9 @@ var SSEClient = class {
|
|
|
3432
4374
|
fetchOptions.headers["Content-Type"] = "application/json";
|
|
3433
4375
|
fetchOptions.body = JSON.stringify(this.options.body);
|
|
3434
4376
|
}
|
|
3435
|
-
const response = await this.
|
|
4377
|
+
const response = await this.withInactivityTimeout(
|
|
4378
|
+
this.fetchImpl(this.url, fetchOptions)
|
|
4379
|
+
);
|
|
3436
4380
|
if (!response.ok) {
|
|
3437
4381
|
const text = await response.text().catch(() => "");
|
|
3438
4382
|
const msg = `SSE connection failed: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`;
|
|
@@ -3455,7 +4399,7 @@ var SSEClient = class {
|
|
|
3455
4399
|
let buffer = "";
|
|
3456
4400
|
try {
|
|
3457
4401
|
while (true) {
|
|
3458
|
-
const { done, value } = await reader.read();
|
|
4402
|
+
const { done, value } = await this.withInactivityTimeout(reader.read());
|
|
3459
4403
|
if (done) break;
|
|
3460
4404
|
buffer += decoder.decode(value, { stream: true });
|
|
3461
4405
|
const blocks = buffer.split("\n\n");
|
|
@@ -3486,9 +4430,48 @@ var SSEClient = class {
|
|
|
3486
4430
|
}
|
|
3487
4431
|
}
|
|
3488
4432
|
} finally {
|
|
3489
|
-
|
|
4433
|
+
try {
|
|
4434
|
+
reader.releaseLock();
|
|
4435
|
+
} catch {
|
|
4436
|
+
}
|
|
3490
4437
|
}
|
|
3491
4438
|
}
|
|
4439
|
+
/**
|
|
4440
|
+
* Race a stream operation against the inactivity timeout.
|
|
4441
|
+
*
|
|
4442
|
+
* If no bytes (or response) arrive within `inactivityTimeoutMs`, the
|
|
4443
|
+
* internal connection is aborted and the operation rejects with a
|
|
4444
|
+
* `RaisinTimeoutError` (code `SSE_INACTIVITY_TIMEOUT`). The timer is
|
|
4445
|
+
* effectively reset on every chunk because each `reader.read()` is
|
|
4446
|
+
* wrapped individually.
|
|
4447
|
+
*/
|
|
4448
|
+
withInactivityTimeout(promise) {
|
|
4449
|
+
const timeoutMs = this.inactivityTimeoutMs;
|
|
4450
|
+
if (!timeoutMs || timeoutMs <= 0) return promise;
|
|
4451
|
+
return new Promise((resolve, reject) => {
|
|
4452
|
+
const timer = setTimeout(() => {
|
|
4453
|
+
promise.catch(() => void 0);
|
|
4454
|
+
this.abortController?.abort();
|
|
4455
|
+
reject(
|
|
4456
|
+
new RaisinTimeoutError(
|
|
4457
|
+
`SSE stream inactive for ${timeoutMs}ms`,
|
|
4458
|
+
"SSE_INACTIVITY_TIMEOUT",
|
|
4459
|
+
timeoutMs
|
|
4460
|
+
)
|
|
4461
|
+
);
|
|
4462
|
+
}, timeoutMs);
|
|
4463
|
+
promise.then(
|
|
4464
|
+
(value) => {
|
|
4465
|
+
clearTimeout(timer);
|
|
4466
|
+
resolve(value);
|
|
4467
|
+
},
|
|
4468
|
+
(error) => {
|
|
4469
|
+
clearTimeout(timer);
|
|
4470
|
+
reject(error);
|
|
4471
|
+
}
|
|
4472
|
+
);
|
|
4473
|
+
});
|
|
4474
|
+
}
|
|
3492
4475
|
/**
|
|
3493
4476
|
* Wait for the reconnect manager to schedule a reconnection attempt.
|
|
3494
4477
|
* Returns true if reconnection was scheduled, false if max attempts reached.
|
|
@@ -3513,9 +4496,27 @@ function isRecoveredDoneEvent(event) {
|
|
|
3513
4496
|
return typeof done.content !== "string" || done.content.trim().length === 0;
|
|
3514
4497
|
}
|
|
3515
4498
|
|
|
4499
|
+
// src/utils/home-path.ts
|
|
4500
|
+
var ACCESS_CONTROL_PREFIX = "/raisin:access_control";
|
|
4501
|
+
function normalizeHomePath(home) {
|
|
4502
|
+
if (home === null || home === void 0) return null;
|
|
4503
|
+
let path = home.trim();
|
|
4504
|
+
if (path.length === 0) return null;
|
|
4505
|
+
if (path === ACCESS_CONTROL_PREFIX) return "/";
|
|
4506
|
+
if (path.startsWith(`${ACCESS_CONTROL_PREFIX}/`)) {
|
|
4507
|
+
path = path.slice(ACCESS_CONTROL_PREFIX.length);
|
|
4508
|
+
}
|
|
4509
|
+
while (path.length > 1 && path.endsWith("/")) {
|
|
4510
|
+
path = path.slice(0, -1);
|
|
4511
|
+
}
|
|
4512
|
+
return path;
|
|
4513
|
+
}
|
|
4514
|
+
|
|
3516
4515
|
// src/conversations.ts
|
|
4516
|
+
var DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS = 12e4;
|
|
3517
4517
|
var ConversationManager = class {
|
|
3518
|
-
constructor(baseUrl, repository, authManager, options = {}, executeSql) {
|
|
4518
|
+
constructor(baseUrl, repository, authManager, options = {}, executeSql, visitor) {
|
|
4519
|
+
this.visitor = visitor;
|
|
3519
4520
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
3520
4521
|
this.repository = repository;
|
|
3521
4522
|
this.authManager = authManager;
|
|
@@ -3535,10 +4536,11 @@ var ConversationManager = class {
|
|
|
3535
4536
|
);
|
|
3536
4537
|
if (!result?.rows?.[0]) throw new Error("User not authenticated");
|
|
3537
4538
|
const row = result.rows[0];
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
4539
|
+
const userHome = normalizeHomePath(row.home);
|
|
4540
|
+
if (!userHome) throw new Error("User home path not found");
|
|
4541
|
+
this.cachedUserHome = userHome;
|
|
4542
|
+
this.cachedUserId = row.user_id || userHome;
|
|
4543
|
+
return { userId: this.cachedUserId, userHome };
|
|
3542
4544
|
}
|
|
3543
4545
|
// ==========================================================================
|
|
3544
4546
|
// Conversation listing
|
|
@@ -3635,6 +4637,45 @@ var ConversationManager = class {
|
|
|
3635
4637
|
]
|
|
3636
4638
|
};
|
|
3637
4639
|
}
|
|
4640
|
+
/**
|
|
4641
|
+
* Start (or resume) an ANONYMOUS conversation with an agent — a public
|
|
4642
|
+
* website's chat, without login.
|
|
4643
|
+
*
|
|
4644
|
+
* Works on an anonymous connection only, and only with an agent that allows
|
|
4645
|
+
* anonymous visitors (`anonymous.enabled: true` on its raisin:AIAgent). The
|
|
4646
|
+
* server keeps the conversation in an ephemeral visitor session that only
|
|
4647
|
+
* this browser session can read; the session secret is kept in
|
|
4648
|
+
* sessionStorage (override with `options.store`), so a reload continues the
|
|
4649
|
+
* same conversation. Then use `sendMessage`, `getMessages`, `subscribe` and
|
|
4650
|
+
* `ConversationStore` / `useConversation` exactly as for a signed-in user.
|
|
4651
|
+
*
|
|
4652
|
+
* @example
|
|
4653
|
+
* ```typescript
|
|
4654
|
+
* const convo = await db.conversations.startAnonymous('/agents/website-assistant');
|
|
4655
|
+
* for await (const ev of db.conversations.sendMessage(convo.conversationPath, 'Hello')) {
|
|
4656
|
+
* if (ev.type === 'text_chunk') append(ev.text);
|
|
4657
|
+
* if (ev.type === 'failed') showLimit(ev.code); // e.g. RATE_LIMITED, LIMIT_REACHED
|
|
4658
|
+
* }
|
|
4659
|
+
* ```
|
|
4660
|
+
*/
|
|
4661
|
+
async startAnonymous(agent, options) {
|
|
4662
|
+
if (!this.visitor) {
|
|
4663
|
+
throw new Error("Anonymous chat needs a WebSocket database: use client.database(repo).");
|
|
4664
|
+
}
|
|
4665
|
+
return this.visitor.start(this.normalizeAgentPath(agent), options);
|
|
4666
|
+
}
|
|
4667
|
+
/** Forget the stored anonymous session for `agent` (the next start begins a new one). */
|
|
4668
|
+
forgetAnonymous(agent, options) {
|
|
4669
|
+
this.visitor?.forget(this.normalizeAgentPath(agent), options);
|
|
4670
|
+
}
|
|
4671
|
+
/** Whether this tab remembers an anonymous conversation with `agent`. */
|
|
4672
|
+
hasStoredAnonymous(agent, options) {
|
|
4673
|
+
return this.visitor?.remembers(this.normalizeAgentPath(agent), options) ?? false;
|
|
4674
|
+
}
|
|
4675
|
+
/** Whether `conversationPath` is an anonymous conversation of this client. */
|
|
4676
|
+
isAnonymous(conversationPath) {
|
|
4677
|
+
return this.visitor?.isAnonymous(conversationPath) ?? false;
|
|
4678
|
+
}
|
|
3638
4679
|
/**
|
|
3639
4680
|
* Open an existing conversation by path.
|
|
3640
4681
|
* Returns null if not found.
|
|
@@ -3680,6 +4721,7 @@ var ConversationManager = class {
|
|
|
3680
4721
|
* Mark a conversation as read by setting unread_count to 0.
|
|
3681
4722
|
*/
|
|
3682
4723
|
async markAsRead(conversationPath) {
|
|
4724
|
+
if (this.isAnonymous(conversationPath)) return;
|
|
3683
4725
|
const workspace = "raisin:access_control";
|
|
3684
4726
|
await this.sqlQuery(
|
|
3685
4727
|
`UPDATE '${workspace}' SET properties = jsonb_set(properties, '{unread_count}', '0'::jsonb) WHERE path = $1 AND node_type = 'raisin:Conversation'`,
|
|
@@ -3690,6 +4732,7 @@ var ConversationManager = class {
|
|
|
3690
4732
|
* Mark a single message as read by the current user.
|
|
3691
4733
|
*/
|
|
3692
4734
|
async markMessageAsRead(messagePath) {
|
|
4735
|
+
if (messagePath.startsWith("/visitors/")) return;
|
|
3693
4736
|
const workspace = "raisin:access_control";
|
|
3694
4737
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3695
4738
|
await this.sqlQuery(
|
|
@@ -3714,6 +4757,18 @@ var ConversationManager = class {
|
|
|
3714
4757
|
*/
|
|
3715
4758
|
async *sendMessage(conversationPath, content, options) {
|
|
3716
4759
|
const shouldStream = options?.stream !== false;
|
|
4760
|
+
if (this.visitor?.isAnonymous(conversationPath)) {
|
|
4761
|
+
if (!shouldStream) {
|
|
4762
|
+
await this.visitor.post(conversationPath, content);
|
|
4763
|
+
yield { type: "waiting", timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4764
|
+
return;
|
|
4765
|
+
}
|
|
4766
|
+
yield* this.visitor.stream(conversationPath, content, {
|
|
4767
|
+
signal: options?.signal,
|
|
4768
|
+
inactivityTimeoutMs: options?.inactivityTimeoutMs ?? DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS
|
|
4769
|
+
});
|
|
4770
|
+
return;
|
|
4771
|
+
}
|
|
3717
4772
|
logger.debug("[ConversationManager] Starting SSE stream for message send");
|
|
3718
4773
|
if (!shouldStream) {
|
|
3719
4774
|
await this.createUserMessage(conversationPath, content);
|
|
@@ -3733,13 +4788,15 @@ var ConversationManager = class {
|
|
|
3733
4788
|
fetch: this.fetchImpl,
|
|
3734
4789
|
signal: options?.signal,
|
|
3735
4790
|
method: "POST",
|
|
3736
|
-
body: { channel: streamTarget.channel, path: streamTarget.path }
|
|
4791
|
+
body: { channel: streamTarget.channel, path: streamTarget.path },
|
|
4792
|
+
inactivityTimeoutMs: options?.inactivityTimeoutMs ?? DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS
|
|
3737
4793
|
});
|
|
3738
4794
|
const iterator = sse[Symbol.asyncIterator]();
|
|
3739
4795
|
let pendingFirst = iterator.next();
|
|
3740
4796
|
try {
|
|
3741
4797
|
await sse.waitUntilConnected();
|
|
3742
4798
|
} catch {
|
|
4799
|
+
pendingFirst?.catch(() => void 0);
|
|
3743
4800
|
pendingFirst = null;
|
|
3744
4801
|
sse.close();
|
|
3745
4802
|
logger.debug("SSE connection failed, falling back to create-only");
|
|
@@ -3776,6 +4833,15 @@ var ConversationManager = class {
|
|
|
3776
4833
|
return;
|
|
3777
4834
|
}
|
|
3778
4835
|
}
|
|
4836
|
+
} catch (error) {
|
|
4837
|
+
if (error instanceof RaisinTimeoutError && error.code === "SSE_INACTIVITY_TIMEOUT") {
|
|
4838
|
+
logger.warn(
|
|
4839
|
+
`[ConversationManager] SSE stream inactive for ${error.timeoutMs}ms, ending turn with synthetic waiting event`
|
|
4840
|
+
);
|
|
4841
|
+
yield { type: "waiting", timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4842
|
+
return;
|
|
4843
|
+
}
|
|
4844
|
+
throw error;
|
|
3779
4845
|
} finally {
|
|
3780
4846
|
sse.close();
|
|
3781
4847
|
}
|
|
@@ -3790,10 +4856,23 @@ var ConversationManager = class {
|
|
|
3790
4856
|
*/
|
|
3791
4857
|
async createUserMessage(conversationPath, content) {
|
|
3792
4858
|
logger.debug("[ConversationManager] Creating user message", { path: conversationPath });
|
|
4859
|
+
if (this.visitor?.isAnonymous(conversationPath)) {
|
|
4860
|
+
await this.visitor.post(conversationPath, content);
|
|
4861
|
+
return;
|
|
4862
|
+
}
|
|
3793
4863
|
const workspace = "raisin:access_control";
|
|
4864
|
+
const properties = await this.loadConversationProperties(conversationPath, workspace);
|
|
4865
|
+
const flowInstanceId = typeof properties?.flow_instance_id === "string" ? properties.flow_instance_id.trim() : "";
|
|
4866
|
+
if (flowInstanceId) {
|
|
4867
|
+
await this.httpRequest({
|
|
4868
|
+
method: "POST",
|
|
4869
|
+
path: `/api/flows/${this.repository}/instances/${encodeURIComponent(flowInstanceId)}/resume`,
|
|
4870
|
+
body: { resume_data: { message: content } }
|
|
4871
|
+
});
|
|
4872
|
+
return;
|
|
4873
|
+
}
|
|
3794
4874
|
const { userId, userHome } = await this.ensureCurrentUser();
|
|
3795
4875
|
const messageId = crypto.randomUUID();
|
|
3796
|
-
const properties = await this.loadConversationProperties(conversationPath, workspace);
|
|
3797
4876
|
const participants = Array.isArray(properties?.participants) ? properties.participants : [];
|
|
3798
4877
|
let recipientId = participants.find(
|
|
3799
4878
|
(p) => typeof p === "string" && p.length > 0 && p !== userId && p !== userHome
|
|
@@ -3832,6 +4911,12 @@ var ConversationManager = class {
|
|
|
3832
4911
|
* initiated messages, etc.). Auto-reconnects on disconnect.
|
|
3833
4912
|
*/
|
|
3834
4913
|
subscribe(conversationPath, onEvent, options) {
|
|
4914
|
+
if (this.visitor?.isAnonymous(conversationPath)) {
|
|
4915
|
+
const stop = this.visitor.listen(conversationPath, onEvent);
|
|
4916
|
+
options?.signal?.addEventListener("abort", stop, { once: true });
|
|
4917
|
+
return { unsubscribe: stop, waitUntilConnected: async () => {
|
|
4918
|
+
} };
|
|
4919
|
+
}
|
|
3835
4920
|
logger.info("[ConversationManager] Subscribing to SSE", { path: conversationPath });
|
|
3836
4921
|
const sseUrl = `${this.baseUrl}/api/conversations/${this.repository}/events`;
|
|
3837
4922
|
const headers = {};
|
|
@@ -3998,7 +5083,17 @@ var ConversationManager = class {
|
|
|
3998
5083
|
tasks.push({
|
|
3999
5084
|
id: row.id,
|
|
4000
5085
|
title: row.properties?.title ?? row.name,
|
|
4001
|
-
status: row.properties?.status ?? "pending"
|
|
5086
|
+
status: row.properties?.status ?? "pending",
|
|
5087
|
+
delegation: row.properties?.delegation_id ? {
|
|
5088
|
+
id: row.properties.delegation_id,
|
|
5089
|
+
status: row.properties.delegation_status ?? "unknown",
|
|
5090
|
+
agentRef: row.properties.delegated_agent_ref?.["raisin:path"] ?? row.properties.delegated_agent_ref?.["raisin:ref"] ?? (typeof row.properties.delegated_agent_ref === "string" ? row.properties.delegated_agent_ref : void 0),
|
|
5091
|
+
flowInstanceId: row.properties.delegation_flow_instance_id ?? void 0,
|
|
5092
|
+
branch: row.properties.delegation_branch ?? void 0,
|
|
5093
|
+
baseBranch: row.properties.delegation_base_branch ?? void 0,
|
|
5094
|
+
result: row.properties.delegation_result,
|
|
5095
|
+
error: row.properties.delegation_error ?? void 0
|
|
5096
|
+
} : void 0
|
|
4002
5097
|
});
|
|
4003
5098
|
tasksByPlan.set(planPath, tasks);
|
|
4004
5099
|
continue;
|
|
@@ -4255,12 +5350,278 @@ var ConversationManager = class {
|
|
|
4255
5350
|
}
|
|
4256
5351
|
async ensureFolderNode(workspace, path, title) {
|
|
4257
5352
|
await this.sqlQuery(
|
|
4258
|
-
`UPSERT INTO '${workspace}' (path, node_type, properties) VALUES ($1, 'raisin:
|
|
5353
|
+
`UPSERT INTO '${workspace}' (path, node_type, properties) VALUES ($1, 'raisin:AclFolder', $2::jsonb)`,
|
|
4259
5354
|
[path, JSON.stringify({ title })]
|
|
4260
5355
|
);
|
|
4261
5356
|
}
|
|
4262
5357
|
};
|
|
4263
5358
|
|
|
5359
|
+
// src/visitor-chat.ts
|
|
5360
|
+
function defaultVisitorSessionStore() {
|
|
5361
|
+
const memory = /* @__PURE__ */ new Map();
|
|
5362
|
+
const storage = () => {
|
|
5363
|
+
try {
|
|
5364
|
+
return typeof sessionStorage !== "undefined" ? sessionStorage : null;
|
|
5365
|
+
} catch {
|
|
5366
|
+
return null;
|
|
5367
|
+
}
|
|
5368
|
+
};
|
|
5369
|
+
return {
|
|
5370
|
+
get(key) {
|
|
5371
|
+
try {
|
|
5372
|
+
return storage()?.getItem(key) ?? memory.get(key) ?? null;
|
|
5373
|
+
} catch {
|
|
5374
|
+
return memory.get(key) ?? null;
|
|
5375
|
+
}
|
|
5376
|
+
},
|
|
5377
|
+
set(key, value) {
|
|
5378
|
+
memory.set(key, value);
|
|
5379
|
+
try {
|
|
5380
|
+
storage()?.setItem(key, value);
|
|
5381
|
+
} catch {
|
|
5382
|
+
}
|
|
5383
|
+
},
|
|
5384
|
+
remove(key) {
|
|
5385
|
+
memory.delete(key);
|
|
5386
|
+
try {
|
|
5387
|
+
storage()?.removeItem(key);
|
|
5388
|
+
} catch {
|
|
5389
|
+
}
|
|
5390
|
+
}
|
|
5391
|
+
};
|
|
5392
|
+
}
|
|
5393
|
+
var VisitorChatError = class extends Error {
|
|
5394
|
+
constructor(message, code) {
|
|
5395
|
+
super(message);
|
|
5396
|
+
this.name = "VisitorChatError";
|
|
5397
|
+
this.code = code;
|
|
5398
|
+
}
|
|
5399
|
+
};
|
|
5400
|
+
var REBIND_CODES = /* @__PURE__ */ new Set(["NO_SESSION", "UNKNOWN_CONVERSATION"]);
|
|
5401
|
+
function asError(err) {
|
|
5402
|
+
const e = err;
|
|
5403
|
+
return new VisitorChatError(e?.message ?? String(err), e?.code ?? "UNAVAILABLE");
|
|
5404
|
+
}
|
|
5405
|
+
var VisitorChat = class {
|
|
5406
|
+
constructor(repository, send, events, onReconnected) {
|
|
5407
|
+
this.repository = repository;
|
|
5408
|
+
this.send = send;
|
|
5409
|
+
this.events = events;
|
|
5410
|
+
this.byPath = /* @__PURE__ */ new Map();
|
|
5411
|
+
this.token = null;
|
|
5412
|
+
onReconnected?.(() => {
|
|
5413
|
+
void this.rebindAll();
|
|
5414
|
+
});
|
|
5415
|
+
}
|
|
5416
|
+
/** Whether `conversationPath` is a visitor conversation started here. */
|
|
5417
|
+
isAnonymous(conversationPath) {
|
|
5418
|
+
return this.byPath.has(conversationPath);
|
|
5419
|
+
}
|
|
5420
|
+
storeOf(options) {
|
|
5421
|
+
if (options.store === false) return null;
|
|
5422
|
+
return options.store ?? defaultVisitorSessionStore();
|
|
5423
|
+
}
|
|
5424
|
+
keyOf(agent, options) {
|
|
5425
|
+
return options.storageKey ?? `raisindb:visitor:${this.repository}:${agent}`;
|
|
5426
|
+
}
|
|
5427
|
+
/** Start (or resume) a visitor conversation with `agent` (`/agents/<name>`). */
|
|
5428
|
+
async start(agent, options = {}) {
|
|
5429
|
+
const store = this.storeOf(options);
|
|
5430
|
+
const key = this.keyOf(agent, options);
|
|
5431
|
+
const sessionToken = options.sessionToken ?? this.token ?? store?.get(`${key}:session`) ?? void 0;
|
|
5432
|
+
const conversationId = options.newConversation ? void 0 : options.conversationId ?? store?.get(`${key}:conversation`) ?? void 0;
|
|
5433
|
+
let res;
|
|
5434
|
+
try {
|
|
5435
|
+
res = await this.send(
|
|
5436
|
+
{ agent, session_token: sessionToken, conversation_id: conversationId },
|
|
5437
|
+
"visitor_chat_start"
|
|
5438
|
+
);
|
|
5439
|
+
} catch (err) {
|
|
5440
|
+
const e = asError(err);
|
|
5441
|
+
if ((conversationId || sessionToken) && !options.conversationId && !options.sessionToken && ["UNKNOWN_CONVERSATION", "INVALID_CONVERSATION", "SESSION_EXPIRED", "SESSION_MISMATCH"].includes(e.code)) {
|
|
5442
|
+
store?.remove(`${key}:conversation`);
|
|
5443
|
+
if (e.code !== "UNKNOWN_CONVERSATION" && e.code !== "INVALID_CONVERSATION") {
|
|
5444
|
+
store?.remove(`${key}:session`);
|
|
5445
|
+
this.token = null;
|
|
5446
|
+
}
|
|
5447
|
+
return this.start(agent, { ...options, newConversation: true });
|
|
5448
|
+
}
|
|
5449
|
+
throw e;
|
|
5450
|
+
}
|
|
5451
|
+
const token = res.session_token ?? sessionToken ?? this.token;
|
|
5452
|
+
if (!token) throw new VisitorChatError("the server returned no session", "UNAVAILABLE");
|
|
5453
|
+
this.token = token;
|
|
5454
|
+
store?.set(`${key}:session`, token);
|
|
5455
|
+
store?.set(`${key}:conversation`, res.conversation_id);
|
|
5456
|
+
const path = res.conversation_path;
|
|
5457
|
+
let entry = this.byPath.get(path);
|
|
5458
|
+
if (!entry) {
|
|
5459
|
+
entry = {
|
|
5460
|
+
agent: res.agent,
|
|
5461
|
+
conversationId: res.conversation_id,
|
|
5462
|
+
subscriptionId: res.subscription_id,
|
|
5463
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
5464
|
+
options
|
|
5465
|
+
};
|
|
5466
|
+
this.byPath.set(path, entry);
|
|
5467
|
+
}
|
|
5468
|
+
this.route(entry);
|
|
5469
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5470
|
+
return {
|
|
5471
|
+
id: res.conversation_id,
|
|
5472
|
+
type: "ai_chat",
|
|
5473
|
+
agentRef: res.agent,
|
|
5474
|
+
conversationPath: path,
|
|
5475
|
+
conversationWorkspace: res.conversation_workspace,
|
|
5476
|
+
anonymous: true,
|
|
5477
|
+
conversationId: res.conversation_id,
|
|
5478
|
+
sessionToken: token,
|
|
5479
|
+
limits: res.limits,
|
|
5480
|
+
initialEvents: [{ type: "waiting", timestamp: now }]
|
|
5481
|
+
};
|
|
5482
|
+
}
|
|
5483
|
+
/** Route the server's forwarded events for this conversation to its listeners. */
|
|
5484
|
+
route(entry) {
|
|
5485
|
+
this.events.removeFlowEventListener(entry.subscriptionId);
|
|
5486
|
+
this.events.addFlowEventListener(entry.subscriptionId, (msg) => {
|
|
5487
|
+
const event = msg.payload;
|
|
5488
|
+
if (!event || typeof event.type !== "string") return;
|
|
5489
|
+
for (const listener of entry.listeners) {
|
|
5490
|
+
try {
|
|
5491
|
+
listener(event);
|
|
5492
|
+
} catch (err) {
|
|
5493
|
+
logger.error("[VisitorChat] listener failed", err);
|
|
5494
|
+
}
|
|
5495
|
+
}
|
|
5496
|
+
});
|
|
5497
|
+
}
|
|
5498
|
+
/** Re-prove the session on a new connection, for every conversation started here. */
|
|
5499
|
+
async rebindAll() {
|
|
5500
|
+
for (const entry of this.byPath.values()) {
|
|
5501
|
+
try {
|
|
5502
|
+
await this.send(
|
|
5503
|
+
{ agent: entry.agent, session_token: this.token, conversation_id: entry.conversationId },
|
|
5504
|
+
"visitor_chat_start"
|
|
5505
|
+
);
|
|
5506
|
+
this.route(entry);
|
|
5507
|
+
} catch (err) {
|
|
5508
|
+
logger.warn("[VisitorChat] could not resume a conversation after reconnect", err);
|
|
5509
|
+
}
|
|
5510
|
+
}
|
|
5511
|
+
}
|
|
5512
|
+
/** Listen to a visitor conversation's events. Returns an unsubscribe function. */
|
|
5513
|
+
listen(conversationPath, listener) {
|
|
5514
|
+
const entry = this.require(conversationPath);
|
|
5515
|
+
entry.listeners.add(listener);
|
|
5516
|
+
return () => {
|
|
5517
|
+
entry.listeners.delete(listener);
|
|
5518
|
+
};
|
|
5519
|
+
}
|
|
5520
|
+
require(conversationPath) {
|
|
5521
|
+
const entry = this.byPath.get(conversationPath);
|
|
5522
|
+
if (!entry) {
|
|
5523
|
+
throw new VisitorChatError(
|
|
5524
|
+
"not a visitor conversation of this client; call startAnonymous first",
|
|
5525
|
+
"UNKNOWN_CONVERSATION"
|
|
5526
|
+
);
|
|
5527
|
+
}
|
|
5528
|
+
return entry;
|
|
5529
|
+
}
|
|
5530
|
+
/** Make sure this connection is bound (after a reconnect nothing else may read the home). */
|
|
5531
|
+
async ensureBound(conversationPath) {
|
|
5532
|
+
const entry = this.require(conversationPath);
|
|
5533
|
+
await this.send(
|
|
5534
|
+
{ agent: entry.agent, session_token: this.token, conversation_id: entry.conversationId },
|
|
5535
|
+
"visitor_chat_start"
|
|
5536
|
+
).catch((err) => {
|
|
5537
|
+
throw asError(err);
|
|
5538
|
+
});
|
|
5539
|
+
}
|
|
5540
|
+
/** Send one visitor message. Events arrive through {@link listen}. */
|
|
5541
|
+
async post(conversationPath, text) {
|
|
5542
|
+
const entry = this.require(conversationPath);
|
|
5543
|
+
const once = async () => await this.send(
|
|
5544
|
+
{ conversation_id: entry.conversationId, text },
|
|
5545
|
+
"visitor_chat_send"
|
|
5546
|
+
);
|
|
5547
|
+
try {
|
|
5548
|
+
const res = await once();
|
|
5549
|
+
return { messageId: res.message_id, messagesLeft: res.messages_left };
|
|
5550
|
+
} catch (err) {
|
|
5551
|
+
const e = asError(err);
|
|
5552
|
+
if (!REBIND_CODES.has(e.code)) throw e;
|
|
5553
|
+
await this.ensureBound(conversationPath);
|
|
5554
|
+
this.route(entry);
|
|
5555
|
+
const res = await once().catch((again) => {
|
|
5556
|
+
throw asError(again);
|
|
5557
|
+
});
|
|
5558
|
+
return { messageId: res.message_id, messagesLeft: res.messages_left };
|
|
5559
|
+
}
|
|
5560
|
+
}
|
|
5561
|
+
/**
|
|
5562
|
+
* Send one visitor message and stream the turn: `text_chunk`,
|
|
5563
|
+
* `tool_call_started`, `tool_call_completed`, … until `done`. A refusal
|
|
5564
|
+
* ends the stream with `failed` (its `code` says why). No event for
|
|
5565
|
+
* `inactivityTimeoutMs` ends it with a synthetic `waiting`.
|
|
5566
|
+
*/
|
|
5567
|
+
async *stream(conversationPath, text, options = {}) {
|
|
5568
|
+
const queue = [];
|
|
5569
|
+
let wake = null;
|
|
5570
|
+
const push = (event) => {
|
|
5571
|
+
queue.push(event);
|
|
5572
|
+
wake?.();
|
|
5573
|
+
};
|
|
5574
|
+
const stop = this.listen(conversationPath, push);
|
|
5575
|
+
const onAbort = () => push({ type: "failed", error: "aborted", code: "ABORTED", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
5576
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
5577
|
+
const timeout = options.inactivityTimeoutMs ?? 12e4;
|
|
5578
|
+
try {
|
|
5579
|
+
try {
|
|
5580
|
+
await this.post(conversationPath, text);
|
|
5581
|
+
} catch (err) {
|
|
5582
|
+
const e = asError(err);
|
|
5583
|
+
yield { type: "failed", error: e.message, code: e.code, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5584
|
+
return;
|
|
5585
|
+
}
|
|
5586
|
+
for (; ; ) {
|
|
5587
|
+
if (!queue.length) {
|
|
5588
|
+
const idle = await new Promise((resolve) => {
|
|
5589
|
+
const timer = timeout > 0 ? setTimeout(() => resolve(true), timeout) : null;
|
|
5590
|
+
wake = () => {
|
|
5591
|
+
if (timer) clearTimeout(timer);
|
|
5592
|
+
resolve(false);
|
|
5593
|
+
};
|
|
5594
|
+
});
|
|
5595
|
+
wake = null;
|
|
5596
|
+
if (idle && !queue.length) {
|
|
5597
|
+
yield { type: "waiting", timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5598
|
+
return;
|
|
5599
|
+
}
|
|
5600
|
+
}
|
|
5601
|
+
const event = queue.shift();
|
|
5602
|
+
yield event;
|
|
5603
|
+
if (event.type === "done" || event.type === "failed" || event.type === "waiting") return;
|
|
5604
|
+
}
|
|
5605
|
+
} finally {
|
|
5606
|
+
stop();
|
|
5607
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
5608
|
+
}
|
|
5609
|
+
}
|
|
5610
|
+
/** Whether the store remembers a conversation with `agent`. */
|
|
5611
|
+
remembers(agent, options = {}) {
|
|
5612
|
+
const store = this.storeOf(options);
|
|
5613
|
+
return !!store?.get(`${this.keyOf(agent, options)}:conversation`);
|
|
5614
|
+
}
|
|
5615
|
+
/** Forget the stored session (e.g. a "new chat" button that should start a new session). */
|
|
5616
|
+
forget(agent, options = {}) {
|
|
5617
|
+
const store = this.storeOf(options);
|
|
5618
|
+
const key = this.keyOf(agent, options);
|
|
5619
|
+
store?.remove(`${key}:session`);
|
|
5620
|
+
store?.remove(`${key}:conversation`);
|
|
5621
|
+
this.token = null;
|
|
5622
|
+
}
|
|
5623
|
+
};
|
|
5624
|
+
|
|
4264
5625
|
// src/flow-client.ts
|
|
4265
5626
|
var FlowClient = class _FlowClient {
|
|
4266
5627
|
/**
|
|
@@ -4538,31 +5899,29 @@ var FlowClient = class _FlowClient {
|
|
|
4538
5899
|
* Respond to a human task within a flow.
|
|
4539
5900
|
*
|
|
4540
5901
|
* When a flow is waiting on a human_task step (approval, input, review),
|
|
4541
|
-
* use this method to submit the response
|
|
5902
|
+
* use this method to submit the response. Completion is validated
|
|
5903
|
+
* (caller must be the task's assignee or an admin) and resumes the flow.
|
|
4542
5904
|
*
|
|
4543
|
-
*
|
|
4544
|
-
*
|
|
4545
|
-
*
|
|
5905
|
+
* Note: prefer `InboxApi` for full inbox functionality (listing tasks,
|
|
5906
|
+
* task details); this is a convenience passthrough to the same endpoint.
|
|
5907
|
+
*
|
|
5908
|
+
* @param instanceId - Flow instance ID (unused; kept for API stability)
|
|
5909
|
+
* @param taskId - The inbox task node ID (or path)
|
|
5910
|
+
* @param response - The response data
|
|
4546
5911
|
*
|
|
4547
5912
|
* @example
|
|
4548
5913
|
* ```typescript
|
|
4549
5914
|
* // Approve a task
|
|
4550
|
-
* await flows.respondToHumanTask('instance-123', '
|
|
5915
|
+
* await flows.respondToHumanTask('instance-123', 'task-abc', {
|
|
4551
5916
|
* action: 'approve',
|
|
4552
5917
|
* comment: 'Looks good!',
|
|
4553
5918
|
* });
|
|
4554
|
-
*
|
|
4555
|
-
* // Provide input for an input task
|
|
4556
|
-
* await flows.respondToHumanTask('instance-123', 'step-3', {
|
|
4557
|
-
* name: 'John Doe',
|
|
4558
|
-
* email: 'john@example.com',
|
|
4559
|
-
* });
|
|
4560
5919
|
* ```
|
|
4561
5920
|
*/
|
|
4562
|
-
async respondToHumanTask(
|
|
5921
|
+
async respondToHumanTask(_instanceId, taskId, response, options) {
|
|
4563
5922
|
await this.request({
|
|
4564
5923
|
method: "POST",
|
|
4565
|
-
path: `/api/
|
|
5924
|
+
path: `/api/inbox/${this.repository}/tasks/${encodeURIComponent(taskId)}/complete`,
|
|
4566
5925
|
body: { response },
|
|
4567
5926
|
signal: options?.signal
|
|
4568
5927
|
});
|
|
@@ -4701,6 +6060,134 @@ var FlowClient = class _FlowClient {
|
|
|
4701
6060
|
}
|
|
4702
6061
|
};
|
|
4703
6062
|
|
|
6063
|
+
// src/inbox.ts
|
|
6064
|
+
var InboxApi = class {
|
|
6065
|
+
constructor(baseUrl, repository, authManager, options = {}) {
|
|
6066
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
6067
|
+
this.repository = repository;
|
|
6068
|
+
this.authManager = authManager;
|
|
6069
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
6070
|
+
this.requestTimeout = options.requestTimeout ?? 3e4;
|
|
6071
|
+
}
|
|
6072
|
+
/**
|
|
6073
|
+
* List the caller's inbox tasks.
|
|
6074
|
+
*
|
|
6075
|
+
* Tasks are sorted pending-first, then by priority (highest first) and
|
|
6076
|
+
* due time (earliest first).
|
|
6077
|
+
*/
|
|
6078
|
+
async listTasks(options = {}) {
|
|
6079
|
+
const params = new URLSearchParams();
|
|
6080
|
+
if (options.status) params.set("status", options.status);
|
|
6081
|
+
if (options.assignee) params.set("assignee", options.assignee);
|
|
6082
|
+
const query = params.size > 0 ? `?${params.toString()}` : "";
|
|
6083
|
+
return this.request({
|
|
6084
|
+
method: "GET",
|
|
6085
|
+
path: `/api/inbox/${this.repository}${query}`,
|
|
6086
|
+
signal: options.signal
|
|
6087
|
+
});
|
|
6088
|
+
}
|
|
6089
|
+
/**
|
|
6090
|
+
* Get a single inbox task by ID (or path).
|
|
6091
|
+
*/
|
|
6092
|
+
async getTask(taskId, options) {
|
|
6093
|
+
return this.request({
|
|
6094
|
+
method: "GET",
|
|
6095
|
+
path: `/api/inbox/${this.repository}/tasks/${encodeURIComponent(taskId)}`,
|
|
6096
|
+
signal: options?.signal
|
|
6097
|
+
});
|
|
6098
|
+
}
|
|
6099
|
+
/**
|
|
6100
|
+
* Complete an inbox task.
|
|
6101
|
+
*
|
|
6102
|
+
* The caller must be the task's assignee (or an admin). Completion marks
|
|
6103
|
+
* the task done, records who decided, and resumes the owning flow.
|
|
6104
|
+
*
|
|
6105
|
+
* @param taskId - Task node ID (or path)
|
|
6106
|
+
* @param response - The response payload:
|
|
6107
|
+
* - approval tasks: `{ action: '<option value>', comment?: string }`
|
|
6108
|
+
* - input tasks: the input value(s) matching the task's input_schema
|
|
6109
|
+
* - review/action tasks: any acknowledgement payload
|
|
6110
|
+
*
|
|
6111
|
+
* @example
|
|
6112
|
+
* ```typescript
|
|
6113
|
+
* await inbox.completeTask('task-123', { action: 'approve' });
|
|
6114
|
+
* ```
|
|
6115
|
+
*/
|
|
6116
|
+
async completeTask(taskId, response, options) {
|
|
6117
|
+
return this.request({
|
|
6118
|
+
method: "POST",
|
|
6119
|
+
path: `/api/inbox/${this.repository}/tasks/${encodeURIComponent(taskId)}/complete`,
|
|
6120
|
+
body: { response },
|
|
6121
|
+
signal: options?.signal
|
|
6122
|
+
});
|
|
6123
|
+
}
|
|
6124
|
+
// ==========================================================================
|
|
6125
|
+
// Internal HTTP request helper
|
|
6126
|
+
// ==========================================================================
|
|
6127
|
+
async request(options) {
|
|
6128
|
+
const url = `${this.baseUrl}${options.path}`;
|
|
6129
|
+
const headers = {
|
|
6130
|
+
"Content-Type": "application/json"
|
|
6131
|
+
};
|
|
6132
|
+
const token = this.authManager.getAccessToken();
|
|
6133
|
+
if (token) {
|
|
6134
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
6135
|
+
}
|
|
6136
|
+
const controller = new AbortController();
|
|
6137
|
+
const timeoutId = setTimeout(() => controller.abort(), this.requestTimeout);
|
|
6138
|
+
if (options.signal) {
|
|
6139
|
+
if (options.signal.aborted) {
|
|
6140
|
+
clearTimeout(timeoutId);
|
|
6141
|
+
throw new RaisinAbortError();
|
|
6142
|
+
}
|
|
6143
|
+
options.signal.addEventListener("abort", () => controller.abort(), {
|
|
6144
|
+
once: true
|
|
6145
|
+
});
|
|
6146
|
+
}
|
|
6147
|
+
try {
|
|
6148
|
+
const response = await this.fetchImpl(url, {
|
|
6149
|
+
method: options.method,
|
|
6150
|
+
headers,
|
|
6151
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
6152
|
+
signal: controller.signal
|
|
6153
|
+
});
|
|
6154
|
+
clearTimeout(timeoutId);
|
|
6155
|
+
if (!response.ok) {
|
|
6156
|
+
const errorText = await response.text();
|
|
6157
|
+
let errorMessage;
|
|
6158
|
+
try {
|
|
6159
|
+
const errorJson = JSON.parse(errorText);
|
|
6160
|
+
errorMessage = errorJson.message || errorJson.error || errorText;
|
|
6161
|
+
} catch {
|
|
6162
|
+
errorMessage = errorText || `HTTP ${response.status}: ${response.statusText}`;
|
|
6163
|
+
}
|
|
6164
|
+
throw classifyHttpError(response.status, errorMessage);
|
|
6165
|
+
}
|
|
6166
|
+
const contentType = response.headers.get("content-type");
|
|
6167
|
+
if (contentType?.includes("application/json")) {
|
|
6168
|
+
return await response.json();
|
|
6169
|
+
}
|
|
6170
|
+
return void 0;
|
|
6171
|
+
} catch (error) {
|
|
6172
|
+
clearTimeout(timeoutId);
|
|
6173
|
+
if (error instanceof RaisinAbortError || error instanceof RaisinTimeoutError) {
|
|
6174
|
+
throw error;
|
|
6175
|
+
}
|
|
6176
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
6177
|
+
if (options.signal?.aborted) {
|
|
6178
|
+
throw new RaisinAbortError();
|
|
6179
|
+
}
|
|
6180
|
+
throw new RaisinTimeoutError(
|
|
6181
|
+
`Request timeout after ${this.requestTimeout}ms`,
|
|
6182
|
+
"REQUEST_TIMEOUT",
|
|
6183
|
+
this.requestTimeout
|
|
6184
|
+
);
|
|
6185
|
+
}
|
|
6186
|
+
throw error;
|
|
6187
|
+
}
|
|
6188
|
+
}
|
|
6189
|
+
};
|
|
6190
|
+
|
|
4704
6191
|
// src/database.ts
|
|
4705
6192
|
var Database = class _Database {
|
|
4706
6193
|
constructor(repository, context, sendRequest, eventHandler, branch, revision, getUploadManager, getSignAssetUrl, httpOptions) {
|
|
@@ -4890,6 +6377,13 @@ var Database = class _Database {
|
|
|
4890
6377
|
tags() {
|
|
4891
6378
|
return new Tags(this._context, this.sendRequest);
|
|
4892
6379
|
}
|
|
6380
|
+
/**
|
|
6381
|
+
* Get SchedulerApi for one-shot scheduled invocations (time-based
|
|
6382
|
+
* function/flow runs).
|
|
6383
|
+
*/
|
|
6384
|
+
scheduler() {
|
|
6385
|
+
return new SchedulerApi(this._context, this.sendRequest);
|
|
6386
|
+
}
|
|
4893
6387
|
/**
|
|
4894
6388
|
* Get FlowsApi for running flows via WebSocket
|
|
4895
6389
|
*/
|
|
@@ -4904,6 +6398,16 @@ var Database = class _Database {
|
|
|
4904
6398
|
}
|
|
4905
6399
|
return this._flowsApi;
|
|
4906
6400
|
}
|
|
6401
|
+
/**
|
|
6402
|
+
* Durable agent runs over this WebSocket connection: find a subject's run,
|
|
6403
|
+
* follow it live (gap-free, resumable), control it, list its children.
|
|
6404
|
+
*/
|
|
6405
|
+
runs() {
|
|
6406
|
+
if (!this._runsApi) {
|
|
6407
|
+
this._runsApi = new AgentRunsWsApi(this._context, this.sendRequest, this.eventHandler);
|
|
6408
|
+
}
|
|
6409
|
+
return this._runsApi;
|
|
6410
|
+
}
|
|
4907
6411
|
/**
|
|
4908
6412
|
* Get FunctionsApi for invoking server-side functions via WebSocket
|
|
4909
6413
|
*/
|
|
@@ -4917,6 +6421,73 @@ var Database = class _Database {
|
|
|
4917
6421
|
}
|
|
4918
6422
|
return this._functionsApi;
|
|
4919
6423
|
}
|
|
6424
|
+
/**
|
|
6425
|
+
* The retrieval implementation behind {@link search} and {@link ask}.
|
|
6426
|
+
*
|
|
6427
|
+
* Private: callers get the two verbs, not the object, so how retrieval is
|
|
6428
|
+
* served stays ours to change.
|
|
6429
|
+
*/
|
|
6430
|
+
searchApi() {
|
|
6431
|
+
if (!this._searchApi) {
|
|
6432
|
+
this._searchApi = new SearchApi(
|
|
6433
|
+
(sql, params) => this.executeSql(sql, params),
|
|
6434
|
+
async (name, input) => {
|
|
6435
|
+
const run = await this.functions().invoke(name, input, {
|
|
6436
|
+
waitForResult: true
|
|
6437
|
+
});
|
|
6438
|
+
return run?.result ?? run;
|
|
6439
|
+
}
|
|
6440
|
+
);
|
|
6441
|
+
}
|
|
6442
|
+
return this._searchApi;
|
|
6443
|
+
}
|
|
6444
|
+
/**
|
|
6445
|
+
* Find the passages matching a query, by meaning and by keyword at once.
|
|
6446
|
+
*
|
|
6447
|
+
* No model is involved — this is the call for a search box or for assembling
|
|
6448
|
+
* your own context. Returns one entry per PASSAGE, so several may come from
|
|
6449
|
+
* one document.
|
|
6450
|
+
*
|
|
6451
|
+
* @example
|
|
6452
|
+
* ```typescript
|
|
6453
|
+
* const passages = await db.search('how much notice to terminate', {
|
|
6454
|
+
* workspaces: 'stories',
|
|
6455
|
+
* });
|
|
6456
|
+
* passages[0].text; // the passage itself
|
|
6457
|
+
* passages[0].chunkIndex; // where in the document it came from
|
|
6458
|
+
* ```
|
|
6459
|
+
*/
|
|
6460
|
+
async search(query, options) {
|
|
6461
|
+
return this.searchApi().search(query, options);
|
|
6462
|
+
}
|
|
6463
|
+
/**
|
|
6464
|
+
* Answer a question from the stored content, with citations.
|
|
6465
|
+
*
|
|
6466
|
+
* Retrieves, judges whether what came back answers the question, rewrites the
|
|
6467
|
+
* query once if it does not, and answers from those passages only. Check
|
|
6468
|
+
* `grounded` before showing the answer: when it is false nothing relevant was
|
|
6469
|
+
* found and the model was never asked.
|
|
6470
|
+
*
|
|
6471
|
+
* `workspaces` is required, the same as on {@link search}: an answer is
|
|
6472
|
+
* quoted back to whoever asked, so the corpus it may be drawn from is stated
|
|
6473
|
+
* rather than assumed. One name, a comma-separated list, a glob, or
|
|
6474
|
+
* `'ALL READABLE'`.
|
|
6475
|
+
*
|
|
6476
|
+
* A method rather than `functions().invoke('ask', ...)` on purpose — see the
|
|
6477
|
+
* note at the top of `search-api.ts`. `invoke` is the escape hatch for
|
|
6478
|
+
* functions YOU wrote.
|
|
6479
|
+
*
|
|
6480
|
+
* @example
|
|
6481
|
+
* ```typescript
|
|
6482
|
+
* const { answer, citations, grounded } = await db.ask(
|
|
6483
|
+
* 'How much notice do we have to give?',
|
|
6484
|
+
* { workspaces: 'stories, handbook' }, // one name, a list, a glob, or 'ALL READABLE'
|
|
6485
|
+
* );
|
|
6486
|
+
* ```
|
|
6487
|
+
*/
|
|
6488
|
+
async ask(question, options) {
|
|
6489
|
+
return this.searchApi().ask(question, options);
|
|
6490
|
+
}
|
|
4920
6491
|
/**
|
|
4921
6492
|
* Get a pre-configured ConversationManager for managing conversations.
|
|
4922
6493
|
*
|
|
@@ -4943,7 +6514,13 @@ var Database = class _Database {
|
|
|
4943
6514
|
this.repository,
|
|
4944
6515
|
this.httpOptions.authManager,
|
|
4945
6516
|
{},
|
|
4946
|
-
(query, params) => this.executeSql(query, params)
|
|
6517
|
+
(query, params) => this.executeSql(query, params),
|
|
6518
|
+
new VisitorChat(
|
|
6519
|
+
this.repository,
|
|
6520
|
+
(payload, requestType) => this.sendRequest(payload, requestType),
|
|
6521
|
+
this.eventHandler,
|
|
6522
|
+
this.httpOptions.onReconnected
|
|
6523
|
+
)
|
|
4947
6524
|
);
|
|
4948
6525
|
}
|
|
4949
6526
|
return this._conversationManager;
|
|
@@ -4977,8 +6554,44 @@ var Database = class _Database {
|
|
|
4977
6554
|
}
|
|
4978
6555
|
return this._flowClient;
|
|
4979
6556
|
}
|
|
6557
|
+
/**
|
|
6558
|
+
* Get a pre-configured InboxApi for human-in-the-loop tasks.
|
|
6559
|
+
*
|
|
6560
|
+
* Requires the Database to have been created via `RaisinClient.database()`
|
|
6561
|
+
* (which automatically provides HTTP context).
|
|
6562
|
+
*
|
|
6563
|
+
* @example
|
|
6564
|
+
* ```typescript
|
|
6565
|
+
* const db = client.database('my-repo');
|
|
6566
|
+
* const { tasks } = await db.inbox.listTasks({ status: 'pending' });
|
|
6567
|
+
* await db.inbox.completeTask(tasks[0].id, { action: 'approve' });
|
|
6568
|
+
* ```
|
|
6569
|
+
*/
|
|
6570
|
+
get inbox() {
|
|
6571
|
+
if (!this._inboxApi) {
|
|
6572
|
+
if (!this.httpOptions?.httpBaseUrl || !this.httpOptions?.authManager) {
|
|
6573
|
+
throw new Error(
|
|
6574
|
+
"db.inbox requires HTTP context. Use client.database() to get a Database with inbox support."
|
|
6575
|
+
);
|
|
6576
|
+
}
|
|
6577
|
+
this._inboxApi = new InboxApi(
|
|
6578
|
+
this.httpOptions.httpBaseUrl,
|
|
6579
|
+
this.repository,
|
|
6580
|
+
this.httpOptions.authManager
|
|
6581
|
+
);
|
|
6582
|
+
}
|
|
6583
|
+
return this._inboxApi;
|
|
6584
|
+
}
|
|
4980
6585
|
/**
|
|
4981
6586
|
* Get EventHandler for subscribing to real-time events
|
|
6587
|
+
*
|
|
6588
|
+
* Path filter semantics for subscriptions (matched server-side):
|
|
6589
|
+
* - A plain path matches **only that exact node** — there is no implicit
|
|
6590
|
+
* prefix matching.
|
|
6591
|
+
* - `*` matches exactly one path segment (`/inbox/*` matches `/inbox/a`,
|
|
6592
|
+
* not `/inbox/a/b`).
|
|
6593
|
+
* - `**` matches recursively (`/inbox/**` matches all descendants of
|
|
6594
|
+
* `/inbox`, but not `/inbox` itself).
|
|
4982
6595
|
*/
|
|
4983
6596
|
events() {
|
|
4984
6597
|
return this.eventHandler;
|
|
@@ -6257,10 +7870,375 @@ var UploadManager = class {
|
|
|
6257
7870
|
}
|
|
6258
7871
|
};
|
|
6259
7872
|
|
|
7873
|
+
// src/http-schema.ts
|
|
7874
|
+
var HttpSchemaBase = class {
|
|
7875
|
+
constructor(repository, branch, client) {
|
|
7876
|
+
this.repository = repository;
|
|
7877
|
+
this.branch = branch;
|
|
7878
|
+
this.client = client;
|
|
7879
|
+
}
|
|
7880
|
+
/** Base management URL for this schema kind. */
|
|
7881
|
+
base() {
|
|
7882
|
+
return `/api/management/${this.repository}/${this.branch}/${this.kind}`;
|
|
7883
|
+
}
|
|
7884
|
+
/** List all schemas (or only published ones). */
|
|
7885
|
+
async list(publishedOnly = false) {
|
|
7886
|
+
const path = publishedOnly ? `${this.base()}/published` : this.base();
|
|
7887
|
+
const result = await this.client.managementRequest("GET", path);
|
|
7888
|
+
return Array.isArray(result) ? result : [];
|
|
7889
|
+
}
|
|
7890
|
+
/** Get a single schema by name. */
|
|
7891
|
+
async get(name) {
|
|
7892
|
+
return this.client.managementRequest("GET", `${this.base()}/${encodeURIComponent(name)}`);
|
|
7893
|
+
}
|
|
7894
|
+
/** Get the schema with full inheritance (`extends`) applied. */
|
|
7895
|
+
async getResolved(name) {
|
|
7896
|
+
return this.client.managementRequest(
|
|
7897
|
+
"GET",
|
|
7898
|
+
`${this.base()}/${encodeURIComponent(name)}/resolved`
|
|
7899
|
+
);
|
|
7900
|
+
}
|
|
7901
|
+
/** Create a schema. `name` is folded into the definition body if absent. */
|
|
7902
|
+
async create(name, definition, commit) {
|
|
7903
|
+
return this.client.managementRequest("POST", this.base(), {
|
|
7904
|
+
[this.defField]: { name, ...definition },
|
|
7905
|
+
commit
|
|
7906
|
+
});
|
|
7907
|
+
}
|
|
7908
|
+
/** Update an existing schema. */
|
|
7909
|
+
async update(name, definition, commit) {
|
|
7910
|
+
return this.client.managementRequest(
|
|
7911
|
+
"PUT",
|
|
7912
|
+
`${this.base()}/${encodeURIComponent(name)}`,
|
|
7913
|
+
{ [this.defField]: { name, ...definition }, commit }
|
|
7914
|
+
);
|
|
7915
|
+
}
|
|
7916
|
+
/** Delete a schema. */
|
|
7917
|
+
async delete(name, commit) {
|
|
7918
|
+
return this.client.managementRequest(
|
|
7919
|
+
"DELETE",
|
|
7920
|
+
`${this.base()}/${encodeURIComponent(name)}`,
|
|
7921
|
+
commit
|
|
7922
|
+
);
|
|
7923
|
+
}
|
|
7924
|
+
/** Publish a schema. */
|
|
7925
|
+
async publish(name, commit) {
|
|
7926
|
+
return this.client.managementRequest(
|
|
7927
|
+
"POST",
|
|
7928
|
+
`${this.base()}/${encodeURIComponent(name)}/publish`,
|
|
7929
|
+
commit
|
|
7930
|
+
);
|
|
7931
|
+
}
|
|
7932
|
+
/** Unpublish a schema. */
|
|
7933
|
+
async unpublish(name, commit) {
|
|
7934
|
+
return this.client.managementRequest(
|
|
7935
|
+
"POST",
|
|
7936
|
+
`${this.base()}/${encodeURIComponent(name)}/unpublish`,
|
|
7937
|
+
commit
|
|
7938
|
+
);
|
|
7939
|
+
}
|
|
7940
|
+
};
|
|
7941
|
+
var HttpNodeTypes = class extends HttpSchemaBase {
|
|
7942
|
+
constructor() {
|
|
7943
|
+
super(...arguments);
|
|
7944
|
+
this.kind = "nodetypes";
|
|
7945
|
+
this.defField = "node_type";
|
|
7946
|
+
}
|
|
7947
|
+
/**
|
|
7948
|
+
* Get the resolved NodeType. Unlike archetypes/element types, NodeType
|
|
7949
|
+
* resolution can be workspace-aware (honors workspace NodeType pins).
|
|
7950
|
+
*/
|
|
7951
|
+
async getResolved(name, opts) {
|
|
7952
|
+
const query = opts?.workspace ? `?workspace=${encodeURIComponent(opts.workspace)}` : "";
|
|
7953
|
+
return this.client.managementRequest(
|
|
7954
|
+
"GET",
|
|
7955
|
+
`${this.base()}/${encodeURIComponent(name)}/resolved${query}`
|
|
7956
|
+
);
|
|
7957
|
+
}
|
|
7958
|
+
};
|
|
7959
|
+
var HttpArchetypes = class extends HttpSchemaBase {
|
|
7960
|
+
constructor() {
|
|
7961
|
+
super(...arguments);
|
|
7962
|
+
this.kind = "archetypes";
|
|
7963
|
+
this.defField = "archetype";
|
|
7964
|
+
}
|
|
7965
|
+
};
|
|
7966
|
+
var HttpElementTypes = class extends HttpSchemaBase {
|
|
7967
|
+
constructor() {
|
|
7968
|
+
super(...arguments);
|
|
7969
|
+
this.kind = "elementtypes";
|
|
7970
|
+
this.defField = "element_type";
|
|
7971
|
+
}
|
|
7972
|
+
};
|
|
7973
|
+
|
|
7974
|
+
// src/mcp.ts
|
|
7975
|
+
var McpClient = class {
|
|
7976
|
+
constructor(transport) {
|
|
7977
|
+
this.transport = transport;
|
|
7978
|
+
}
|
|
7979
|
+
/**
|
|
7980
|
+
* List the tools this MCP server advertises to the calling identity.
|
|
7981
|
+
*
|
|
7982
|
+
* Wraps the `tools/list` JSON-RPC method. The visible set is RLS-scoped to
|
|
7983
|
+
* the authenticated caller (or the anonymous role).
|
|
7984
|
+
*
|
|
7985
|
+
* @returns The advertised tool descriptors.
|
|
7986
|
+
*/
|
|
7987
|
+
async listTools() {
|
|
7988
|
+
const result = await this.transport.rpc("tools/list");
|
|
7989
|
+
return { tools: result?.tools ?? [] };
|
|
7990
|
+
}
|
|
7991
|
+
/**
|
|
7992
|
+
* Invoke a tool by name.
|
|
7993
|
+
*
|
|
7994
|
+
* Wraps the `tools/call` JSON-RPC method. The underlying `raisin:Function`
|
|
7995
|
+
* runs RLS-scoped to the calling identity. A domain-level tool failure is
|
|
7996
|
+
* reported via {@link McpCallToolResult.isError} (not thrown); transport and
|
|
7997
|
+
* protocol errors are thrown.
|
|
7998
|
+
*
|
|
7999
|
+
* @param name - Tool name from {@link listTools}.
|
|
8000
|
+
* @param args - Arguments object matching the tool's `inputSchema`.
|
|
8001
|
+
* @returns The tool's content blocks and optional structured content.
|
|
8002
|
+
*/
|
|
8003
|
+
async callTool(name, args) {
|
|
8004
|
+
const result = await this.transport.rpc("tools/call", {
|
|
8005
|
+
name,
|
|
8006
|
+
arguments: args ?? {}
|
|
8007
|
+
});
|
|
8008
|
+
return result;
|
|
8009
|
+
}
|
|
8010
|
+
/**
|
|
8011
|
+
* Read a resource by its `raisin://` URI.
|
|
8012
|
+
*
|
|
8013
|
+
* Wraps the `resources/read` JSON-RPC method. JSON-only resources return
|
|
8014
|
+
* their properties as a `text` entry; byte-serving resources return base64
|
|
8015
|
+
* in `blob`.
|
|
8016
|
+
*
|
|
8017
|
+
* @param uri - `raisin://{workspace}/{path}` URI to read.
|
|
8018
|
+
* @returns One content entry per URI read.
|
|
8019
|
+
*/
|
|
8020
|
+
async readResource(uri) {
|
|
8021
|
+
const result = await this.transport.rpc("resources/read", {
|
|
8022
|
+
uri
|
|
8023
|
+
});
|
|
8024
|
+
return { contents: result?.contents ?? [] };
|
|
8025
|
+
}
|
|
8026
|
+
/**
|
|
8027
|
+
* Subscribe to change notifications for a resource URI (or URI prefix).
|
|
8028
|
+
*
|
|
8029
|
+
* Opens a `subscriptions/listen` stream (SSE) with a `resourceSubscriptions`
|
|
8030
|
+
* filter naming the URI. The returned {@link McpResourceSubscription} is an
|
|
8031
|
+
* async iterable of {@link McpResourceUpdate}; the server's acknowledgement
|
|
8032
|
+
* frame and the closing response are consumed internally and not yielded.
|
|
8033
|
+
* Call `close()` to end the stream.
|
|
8034
|
+
*
|
|
8035
|
+
* @param uri - `raisin://` URI (or prefix) to watch.
|
|
8036
|
+
* @returns A live, closeable subscription.
|
|
8037
|
+
*/
|
|
8038
|
+
subscribeResource(uri) {
|
|
8039
|
+
const stream = this.transport.subscribe("subscriptions/listen", {
|
|
8040
|
+
notifications: { resourceSubscriptions: [uri] }
|
|
8041
|
+
});
|
|
8042
|
+
return {
|
|
8043
|
+
close: () => stream.close(),
|
|
8044
|
+
async *[Symbol.asyncIterator]() {
|
|
8045
|
+
for await (const frame of stream) {
|
|
8046
|
+
if (frame.method === "notifications/resources/updated") {
|
|
8047
|
+
const params = frame.params ?? {};
|
|
8048
|
+
if (typeof params.uri === "string") {
|
|
8049
|
+
yield { uri: params.uri };
|
|
8050
|
+
}
|
|
8051
|
+
}
|
|
8052
|
+
}
|
|
8053
|
+
}
|
|
8054
|
+
};
|
|
8055
|
+
}
|
|
8056
|
+
};
|
|
8057
|
+
|
|
8058
|
+
// src/identity-auth.ts
|
|
8059
|
+
var IdentityAuthApi = class {
|
|
8060
|
+
constructor(repository, transport) {
|
|
8061
|
+
this.repository = repository;
|
|
8062
|
+
this.transport = transport;
|
|
8063
|
+
}
|
|
8064
|
+
get base() {
|
|
8065
|
+
return `/auth/${encodeURIComponent(this.repository)}`;
|
|
8066
|
+
}
|
|
8067
|
+
/**
|
|
8068
|
+
* Ask RaisinDB to email a sign-in link.
|
|
8069
|
+
*
|
|
8070
|
+
* The reply is an acknowledgement, never a token — the token only exists in
|
|
8071
|
+
* the email. It also resolves for an address that has no account, because a
|
|
8072
|
+
* response that differed would turn this endpoint into an account-existence
|
|
8073
|
+
* oracle.
|
|
8074
|
+
*/
|
|
8075
|
+
async sendMagicLink(email, options) {
|
|
8076
|
+
return this.transport({
|
|
8077
|
+
method: "POST",
|
|
8078
|
+
path: `${this.base}/magic-link`,
|
|
8079
|
+
body: { email, redirect_url: options?.redirectUrl },
|
|
8080
|
+
skipAuth: true
|
|
8081
|
+
});
|
|
8082
|
+
}
|
|
8083
|
+
/**
|
|
8084
|
+
* Exchange a magic-link token for a session.
|
|
8085
|
+
*
|
|
8086
|
+
* Most applications never call this: following the emailed link hits
|
|
8087
|
+
* RaisinDB directly, which verifies and 302s to the redirect URL with the
|
|
8088
|
+
* tokens in the URL **fragment** (so they stay out of server logs and out
|
|
8089
|
+
* of the Referer header) — see {@link readTokensFromFragment}. Call this
|
|
8090
|
+
* only when handling the token yourself, and note it is single-use.
|
|
8091
|
+
*/
|
|
8092
|
+
async verifyMagicLink(token) {
|
|
8093
|
+
return this.transport({
|
|
8094
|
+
method: "GET",
|
|
8095
|
+
path: `${this.base}/magic-link/verify?token=${encodeURIComponent(token)}`,
|
|
8096
|
+
skipAuth: true
|
|
8097
|
+
});
|
|
8098
|
+
}
|
|
8099
|
+
/**
|
|
8100
|
+
* Sign in with email and password, where the repository enables it.
|
|
8101
|
+
*
|
|
8102
|
+
* `rememberMe` chooses the lifetime of the sign-in: the session AND its refresh
|
|
8103
|
+
* token last 30 days with it and 24 hours without (both configurable per tenant:
|
|
8104
|
+
* `refresh_token_duration_days`, `refresh_token_session_hours`). Refreshing never
|
|
8105
|
+
* extends a session past that.
|
|
8106
|
+
*/
|
|
8107
|
+
async login(email, password, options) {
|
|
8108
|
+
return this.transport({
|
|
8109
|
+
method: "POST",
|
|
8110
|
+
path: `${this.base}/login`,
|
|
8111
|
+
body: { email, password, remember_me: options?.rememberMe ?? false },
|
|
8112
|
+
skipAuth: true
|
|
8113
|
+
});
|
|
8114
|
+
}
|
|
8115
|
+
/** Create an account with email and password. */
|
|
8116
|
+
async register(email, password, options) {
|
|
8117
|
+
return this.transport({
|
|
8118
|
+
method: "POST",
|
|
8119
|
+
path: `${this.base}/register`,
|
|
8120
|
+
body: { email, password, display_name: options?.displayName },
|
|
8121
|
+
skipAuth: true
|
|
8122
|
+
});
|
|
8123
|
+
}
|
|
8124
|
+
/** Trade a refresh token for a fresh pair. */
|
|
8125
|
+
async refresh(refreshToken) {
|
|
8126
|
+
return this.transport({
|
|
8127
|
+
method: "POST",
|
|
8128
|
+
path: `${this.base}/refresh`,
|
|
8129
|
+
body: { refresh_token: refreshToken },
|
|
8130
|
+
skipAuth: true
|
|
8131
|
+
});
|
|
8132
|
+
}
|
|
8133
|
+
/**
|
|
8134
|
+
* Which sign-in methods to render.
|
|
8135
|
+
*
|
|
8136
|
+
* Ask rather than hard-code: magic link and password are per-repository
|
|
8137
|
+
* configuration, and a button for a disabled method is a dead end.
|
|
8138
|
+
*/
|
|
8139
|
+
async providers() {
|
|
8140
|
+
return this.transport({
|
|
8141
|
+
method: "GET",
|
|
8142
|
+
path: `${this.base}/providers`,
|
|
8143
|
+
skipAuth: true
|
|
8144
|
+
});
|
|
8145
|
+
}
|
|
8146
|
+
/** Who the current token belongs to in this repository. */
|
|
8147
|
+
async me() {
|
|
8148
|
+
return this.transport({ method: "GET", path: `${this.base}/me` });
|
|
8149
|
+
}
|
|
8150
|
+
};
|
|
8151
|
+
function readTokensFromFragment(hashOrUrl) {
|
|
8152
|
+
const hash = hashOrUrl.includes("#") ? hashOrUrl.slice(hashOrUrl.indexOf("#") + 1) : hashOrUrl;
|
|
8153
|
+
if (!hash) return null;
|
|
8154
|
+
const params = new URLSearchParams(hash);
|
|
8155
|
+
const accessToken = params.get("access_token");
|
|
8156
|
+
if (!accessToken) return null;
|
|
8157
|
+
return { accessToken, refreshToken: params.get("refresh_token") };
|
|
8158
|
+
}
|
|
8159
|
+
|
|
8160
|
+
// src/node-dev.ts
|
|
8161
|
+
var NodeDevApi = class {
|
|
8162
|
+
constructor(repository, transport) {
|
|
8163
|
+
this.repository = repository;
|
|
8164
|
+
this.transport = transport;
|
|
8165
|
+
}
|
|
8166
|
+
/** Call any method by name (the typed helpers below all use this). */
|
|
8167
|
+
call(method, request) {
|
|
8168
|
+
const branch = typeof request.branch === "string" ? `?branch=${encodeURIComponent(request.branch)}` : "";
|
|
8169
|
+
return this.transport.request(
|
|
8170
|
+
"POST",
|
|
8171
|
+
`/api/node-dev/${encodeURIComponent(this.repository)}/${method}${branch}`,
|
|
8172
|
+
request
|
|
8173
|
+
);
|
|
8174
|
+
}
|
|
8175
|
+
changeset(o) {
|
|
8176
|
+
const { idempotencyKey, ...rest } = o;
|
|
8177
|
+
return { ...rest, idempotency_key: idempotencyKey };
|
|
8178
|
+
}
|
|
8179
|
+
stat(target, o = {}) {
|
|
8180
|
+
return this.call("stat", { ...o, target });
|
|
8181
|
+
}
|
|
8182
|
+
read(target, o = {}) {
|
|
8183
|
+
return this.call("read", { ...o, target });
|
|
8184
|
+
}
|
|
8185
|
+
list(target, o = {}) {
|
|
8186
|
+
return this.call("list", { ...o, target });
|
|
8187
|
+
}
|
|
8188
|
+
diff(target, from, to, o = {}) {
|
|
8189
|
+
return this.call("diff", { ...o, target, from, to });
|
|
8190
|
+
}
|
|
8191
|
+
/** Changes inside the roots after `since` (a cursor from a previous call). */
|
|
8192
|
+
watch(since, o = {}) {
|
|
8193
|
+
return this.call("watch", { ...o, since });
|
|
8194
|
+
}
|
|
8195
|
+
dryRun(o) {
|
|
8196
|
+
return this.call("dry_run", this.changeset(o));
|
|
8197
|
+
}
|
|
8198
|
+
propose(o) {
|
|
8199
|
+
return this.call(
|
|
8200
|
+
"propose",
|
|
8201
|
+
this.changeset(o)
|
|
8202
|
+
);
|
|
8203
|
+
}
|
|
8204
|
+
getChangeset(changesetId, o = {}) {
|
|
8205
|
+
return this.call("get_changeset", { ...o, changeset_id: changesetId });
|
|
8206
|
+
}
|
|
8207
|
+
listChangesets(o = {}) {
|
|
8208
|
+
return this.call("list_changesets", { ...o });
|
|
8209
|
+
}
|
|
8210
|
+
/** Commit; pass the reviewed digest to bind the commit to that approval. */
|
|
8211
|
+
commit(changesetId, expectedDigest, o = {}) {
|
|
8212
|
+
return this.call("commit", { ...o, changeset_id: changesetId, expected_digest: expectedDigest });
|
|
8213
|
+
}
|
|
8214
|
+
discard(changesetId, o = {}) {
|
|
8215
|
+
return this.call("discard", { ...o, changeset_id: changesetId });
|
|
8216
|
+
}
|
|
8217
|
+
/** Propose and commit in one call. */
|
|
8218
|
+
apply(o) {
|
|
8219
|
+
return this.call("apply", this.changeset(o));
|
|
8220
|
+
}
|
|
8221
|
+
forkBranch(name, o = {}) {
|
|
8222
|
+
return this.call("fork_branch", { ...o, name });
|
|
8223
|
+
}
|
|
8224
|
+
diffBranch(base = "main", o = {}) {
|
|
8225
|
+
return this.call("diff_branch", { ...o, base });
|
|
8226
|
+
}
|
|
8227
|
+
mergeBranch(target, o = {}) {
|
|
8228
|
+
const { dryRun, ...rest } = o;
|
|
8229
|
+
return this.call("merge_branch", { ...rest, target, dry_run: dryRun });
|
|
8230
|
+
}
|
|
8231
|
+
discardBranch(o = {}) {
|
|
8232
|
+
return this.call("discard_branch", { ...o });
|
|
8233
|
+
}
|
|
8234
|
+
};
|
|
8235
|
+
|
|
6260
8236
|
// src/http-client.ts
|
|
6261
8237
|
var RaisinHttpClient = class extends import_events3.EventEmitter {
|
|
6262
8238
|
constructor(baseUrl, options = {}) {
|
|
6263
8239
|
super();
|
|
8240
|
+
/** Monotonic JSON-RPC id counter for MCP requests. */
|
|
8241
|
+
this._mcpRequestId = 0;
|
|
6264
8242
|
this._currentUser = null;
|
|
6265
8243
|
this._uploadManager = null;
|
|
6266
8244
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
@@ -6288,6 +8266,7 @@ var RaisinHttpClient = class extends import_events3.EventEmitter {
|
|
|
6288
8266
|
async authenticate(credentials, repository) {
|
|
6289
8267
|
if (isJwtCredentials(credentials)) {
|
|
6290
8268
|
this.authManager.storage.setAccessToken(credentials.token);
|
|
8269
|
+
this.authManager.adoptToken(credentials.token);
|
|
6291
8270
|
if (repository) {
|
|
6292
8271
|
try {
|
|
6293
8272
|
const userInfo = await this.fetchCurrentUser(repository);
|
|
@@ -6306,9 +8285,13 @@ var RaisinHttpClient = class extends import_events3.EventEmitter {
|
|
|
6306
8285
|
body: payload,
|
|
6307
8286
|
skipAuth: true
|
|
6308
8287
|
});
|
|
6309
|
-
|
|
8288
|
+
if (response.data.token) {
|
|
8289
|
+
this.authManager.storage.setAccessToken(response.data.token);
|
|
8290
|
+
} else {
|
|
8291
|
+
this.authManager.setTokens(response.data);
|
|
8292
|
+
}
|
|
6310
8293
|
this._currentUser = {
|
|
6311
|
-
userId: "admin",
|
|
8294
|
+
userId: response.data.user_id ?? "admin",
|
|
6312
8295
|
anonymous: false
|
|
6313
8296
|
};
|
|
6314
8297
|
this.emit("authenticated", response.data);
|
|
@@ -6649,6 +8632,66 @@ var RaisinHttpClient = class extends import_events3.EventEmitter {
|
|
|
6649
8632
|
return this.authManager;
|
|
6650
8633
|
}
|
|
6651
8634
|
// ============================================================================
|
|
8635
|
+
// Identity Authentication
|
|
8636
|
+
// ============================================================================
|
|
8637
|
+
/**
|
|
8638
|
+
* Identity authentication (magic link, password, providers) for one
|
|
8639
|
+
* repository.
|
|
8640
|
+
*
|
|
8641
|
+
* Identities are per repository — their home nodes and roles live there — so
|
|
8642
|
+
* the repository is required rather than inferred from client state, which
|
|
8643
|
+
* would silently sign somebody into whichever one happened to be current.
|
|
8644
|
+
*
|
|
8645
|
+
* ```ts
|
|
8646
|
+
* const auth = client.auth('studio');
|
|
8647
|
+
* await auth.sendMagicLink('a@example.com', {
|
|
8648
|
+
* redirectUrl: 'https://app.example.com/account/callback',
|
|
8649
|
+
* });
|
|
8650
|
+
* // …on the callback route:
|
|
8651
|
+
* const tokens = readTokensFromFragment(window.location.hash);
|
|
8652
|
+
* if (tokens) client.setIdentityTokens(tokens.accessToken, tokens.refreshToken);
|
|
8653
|
+
* ```
|
|
8654
|
+
*/
|
|
8655
|
+
auth(repository) {
|
|
8656
|
+
return new IdentityAuthApi(repository, async (options) => {
|
|
8657
|
+
const response = await this.request({
|
|
8658
|
+
method: options.method,
|
|
8659
|
+
path: options.path,
|
|
8660
|
+
body: options.body,
|
|
8661
|
+
skipAuth: options.skipAuth
|
|
8662
|
+
});
|
|
8663
|
+
return response.data;
|
|
8664
|
+
});
|
|
8665
|
+
}
|
|
8666
|
+
/**
|
|
8667
|
+
* Adopt an identity's tokens for every subsequent call on this client.
|
|
8668
|
+
*
|
|
8669
|
+
* Takes the loose form so it serves both callers: the whole result of a
|
|
8670
|
+
* sign-in, and the pair recovered from a redirect fragment (which carries no
|
|
8671
|
+
* identity object and may carry no refresh token).
|
|
8672
|
+
*/
|
|
8673
|
+
setIdentityTokens(accessTokenOrResult, refreshToken) {
|
|
8674
|
+
if (typeof accessTokenOrResult === "string") {
|
|
8675
|
+
this.authManager.storage.setAccessToken(accessTokenOrResult);
|
|
8676
|
+
this.authManager.adoptToken(accessTokenOrResult);
|
|
8677
|
+
if (refreshToken) this.authManager.storage.setRefreshToken(refreshToken);
|
|
8678
|
+
return;
|
|
8679
|
+
}
|
|
8680
|
+
const result = accessTokenOrResult;
|
|
8681
|
+
this.authManager.setTokens({
|
|
8682
|
+
access_token: result.access_token,
|
|
8683
|
+
refresh_token: result.refresh_token,
|
|
8684
|
+
// The server reports an absolute expiry; AuthManager schedules from a
|
|
8685
|
+
// duration. Deriving it here keeps the refresh timer honest instead of
|
|
8686
|
+
// leaving it unset, which reads as "never expires".
|
|
8687
|
+
expires_in: Math.max(0, result.expires_at - Math.floor(Date.now() / 1e3))
|
|
8688
|
+
});
|
|
8689
|
+
}
|
|
8690
|
+
/** Forget the current identity's tokens. */
|
|
8691
|
+
clearIdentityTokens() {
|
|
8692
|
+
this.authManager.clear();
|
|
8693
|
+
}
|
|
8694
|
+
// ============================================================================
|
|
6652
8695
|
// Upload Methods
|
|
6653
8696
|
// ============================================================================
|
|
6654
8697
|
/**
|
|
@@ -6850,7 +8893,7 @@ var RaisinHttpClient = class extends import_events3.EventEmitter {
|
|
|
6850
8893
|
"Content-Type": "application/json",
|
|
6851
8894
|
...options.headers
|
|
6852
8895
|
};
|
|
6853
|
-
if (!options.skipAuth
|
|
8896
|
+
if (!options.skipAuth) {
|
|
6854
8897
|
const token = this.authManager.getAccessToken();
|
|
6855
8898
|
if (token) {
|
|
6856
8899
|
headers["Authorization"] = `Bearer ${token}`;
|
|
@@ -6915,6 +8958,33 @@ var RaisinHttpClient = class extends import_events3.EventEmitter {
|
|
|
6915
8958
|
* @param input - Input data passed to the function
|
|
6916
8959
|
* @returns Execution ID and job ID for tracking
|
|
6917
8960
|
*/
|
|
8961
|
+
/**
|
|
8962
|
+
* Durable agent runs of `repository`: create, read, control, stream, and
|
|
8963
|
+
* drive them directly (see {@link AgentRunsApi}).
|
|
8964
|
+
*/
|
|
8965
|
+
agentRuns(repository) {
|
|
8966
|
+
return new AgentRunsApi(repository, {
|
|
8967
|
+
request: async (method, path, body) => (await this.request({ method, path, body })).data,
|
|
8968
|
+
url: (path) => `${this.baseUrl}${path}`,
|
|
8969
|
+
headers: () => {
|
|
8970
|
+
const token = this.authManager.getAccessToken();
|
|
8971
|
+
const headers = {};
|
|
8972
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
8973
|
+
return headers;
|
|
8974
|
+
},
|
|
8975
|
+
fetch: this.fetchImpl
|
|
8976
|
+
});
|
|
8977
|
+
}
|
|
8978
|
+
/**
|
|
8979
|
+
* The node-development surface of `repository`: typed reads, atomic
|
|
8980
|
+
* changesets with review and idempotency, branch worktrees (see
|
|
8981
|
+
* {@link NodeDevApi}).
|
|
8982
|
+
*/
|
|
8983
|
+
nodeDev(repository) {
|
|
8984
|
+
return new NodeDevApi(repository, {
|
|
8985
|
+
request: async (method, path, body) => (await this.request({ method, path, body })).data
|
|
8986
|
+
});
|
|
8987
|
+
}
|
|
6918
8988
|
async invokeFunction(repository, functionName, input, options) {
|
|
6919
8989
|
const requestTimeoutMs = options?.requestTimeoutMs ?? this.options.requestTimeout;
|
|
6920
8990
|
let waitTimeoutMs = options?.waitTimeoutMs;
|
|
@@ -6952,6 +9022,105 @@ var RaisinHttpClient = class extends import_events3.EventEmitter {
|
|
|
6952
9022
|
});
|
|
6953
9023
|
return response.data;
|
|
6954
9024
|
}
|
|
9025
|
+
/**
|
|
9026
|
+
* Send one JSON-RPC request to an MCP server endpoint and return its
|
|
9027
|
+
* unwrapped `result`.
|
|
9028
|
+
*
|
|
9029
|
+
* Calls `POST /mcp/{repo}/{branch}/{slug}` carrying a single JSON-RPC 2.0
|
|
9030
|
+
* message. MCP errors are returned in the JSON-RPC envelope (HTTP `200`), so
|
|
9031
|
+
* this method inspects the body's `error` member and throws on failure.
|
|
9032
|
+
*
|
|
9033
|
+
* @internal Used by {@link McpClient} via the transport built in `mcp()`.
|
|
9034
|
+
*/
|
|
9035
|
+
async mcpRpc(repository, branch, slug, method, params) {
|
|
9036
|
+
const id = ++this._mcpRequestId;
|
|
9037
|
+
const response = await this.request({
|
|
9038
|
+
method: "POST",
|
|
9039
|
+
path: `/mcp/${repository}/${branch}/${slug}`,
|
|
9040
|
+
body: {
|
|
9041
|
+
jsonrpc: "2.0",
|
|
9042
|
+
id,
|
|
9043
|
+
method,
|
|
9044
|
+
...params !== void 0 ? { params } : {}
|
|
9045
|
+
}
|
|
9046
|
+
});
|
|
9047
|
+
const frame = response.data;
|
|
9048
|
+
if (frame?.error) {
|
|
9049
|
+
const err = new Error(frame.error.message || `MCP error ${frame.error.code}`);
|
|
9050
|
+
err.code = frame.error.code;
|
|
9051
|
+
err.data = frame.error.data;
|
|
9052
|
+
throw err;
|
|
9053
|
+
}
|
|
9054
|
+
return frame?.result;
|
|
9055
|
+
}
|
|
9056
|
+
/**
|
|
9057
|
+
* Open the `subscriptions/listen` SSE stream for an MCP server endpoint.
|
|
9058
|
+
*
|
|
9059
|
+
* Streams JSON-RPC frames (the subscription ack followed by
|
|
9060
|
+
* `notifications/resources/updated` frames) from
|
|
9061
|
+
* `POST /mcp/{repo}/{branch}/{slug}`.
|
|
9062
|
+
*
|
|
9063
|
+
* @internal Used by {@link McpClient} via the transport built in `mcp()`.
|
|
9064
|
+
*/
|
|
9065
|
+
mcpSubscribe(repository, branch, slug, method, params) {
|
|
9066
|
+
const headers = {};
|
|
9067
|
+
const token = this.authManager.getAccessToken();
|
|
9068
|
+
if (token) {
|
|
9069
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
9070
|
+
}
|
|
9071
|
+
const sse = new SSEClient(
|
|
9072
|
+
`${this.baseUrl}/mcp/${repository}/${branch}/${slug}`,
|
|
9073
|
+
{
|
|
9074
|
+
method: "POST",
|
|
9075
|
+
body: {
|
|
9076
|
+
jsonrpc: "2.0",
|
|
9077
|
+
id: ++this._mcpRequestId,
|
|
9078
|
+
method,
|
|
9079
|
+
params
|
|
9080
|
+
},
|
|
9081
|
+
headers,
|
|
9082
|
+
fetch: this.fetchImpl,
|
|
9083
|
+
reconnect: { enabled: false }
|
|
9084
|
+
}
|
|
9085
|
+
);
|
|
9086
|
+
return {
|
|
9087
|
+
close: () => sse.close(),
|
|
9088
|
+
async *[Symbol.asyncIterator]() {
|
|
9089
|
+
for await (const event of sse) {
|
|
9090
|
+
yield event.data;
|
|
9091
|
+
}
|
|
9092
|
+
}
|
|
9093
|
+
};
|
|
9094
|
+
}
|
|
9095
|
+
/**
|
|
9096
|
+
* Get an {@link McpClient} for a content-declared MCP server.
|
|
9097
|
+
*
|
|
9098
|
+
* @param slug - The `raisin:McpServer` slug (resolved in the `mcp` workspace).
|
|
9099
|
+
* @param branch - Branch to address; defaults to the client's current branch.
|
|
9100
|
+
*/
|
|
9101
|
+
mcp(slug, branch) {
|
|
9102
|
+
const repository = this._context.repository;
|
|
9103
|
+
if (!repository) {
|
|
9104
|
+
throw new Error("mcp() requires a repository; call database(name) first");
|
|
9105
|
+
}
|
|
9106
|
+
const effectiveBranch = branch ?? this.getBranch();
|
|
9107
|
+
const transport = {
|
|
9108
|
+
rpc: (m, p) => this.mcpRpc(repository, effectiveBranch, slug, m, p),
|
|
9109
|
+
subscribe: (m, p) => this.mcpSubscribe(repository, effectiveBranch, slug, m, p)
|
|
9110
|
+
};
|
|
9111
|
+
return new McpClient(transport);
|
|
9112
|
+
}
|
|
9113
|
+
/**
|
|
9114
|
+
* Make a management REST request and return the parsed body.
|
|
9115
|
+
*
|
|
9116
|
+
* @internal Used by the HTTP schema-management helpers
|
|
9117
|
+
* ({@link HttpNodeTypes}/{@link HttpArchetypes}/{@link HttpElementTypes}) to
|
|
9118
|
+
* reuse the client's centralized auth, timeout, and base-URL handling.
|
|
9119
|
+
*/
|
|
9120
|
+
async managementRequest(method, path, body) {
|
|
9121
|
+
const response = await this.request({ method, path, body });
|
|
9122
|
+
return response.data;
|
|
9123
|
+
}
|
|
6955
9124
|
/**
|
|
6956
9125
|
* Get a simplified database interface for SSR
|
|
6957
9126
|
* This returns an HTTP-based database client
|
|
@@ -6979,19 +9148,6 @@ var HttpDatabase = class _HttpDatabase {
|
|
|
6979
9148
|
getBranch() {
|
|
6980
9149
|
return this.branch;
|
|
6981
9150
|
}
|
|
6982
|
-
/**
|
|
6983
|
-
* Create a new HttpDatabase instance scoped to a specific branch.
|
|
6984
|
-
*
|
|
6985
|
-
* @param branch - Branch name
|
|
6986
|
-
* @returns New HttpDatabase instance with branch context
|
|
6987
|
-
*
|
|
6988
|
-
* @example
|
|
6989
|
-
* ```typescript
|
|
6990
|
-
* const db = client.database('my-repo');
|
|
6991
|
-
* const staging = db.onBranch('staging');
|
|
6992
|
-
* const result = await staging.executeSql('SELECT * FROM content');
|
|
6993
|
-
* ```
|
|
6994
|
-
*/
|
|
6995
9151
|
onBranch(branch) {
|
|
6996
9152
|
return new _HttpDatabase(this.repository, this.client, branch);
|
|
6997
9153
|
}
|
|
@@ -7032,12 +9188,107 @@ var HttpDatabase = class _HttpDatabase {
|
|
|
7032
9188
|
/**
|
|
7033
9189
|
* Get FunctionsApi for invoking server-side functions via HTTP
|
|
7034
9190
|
*/
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
9191
|
+
/** Durable agent runs of this repository. */
|
|
9192
|
+
agentRuns() {
|
|
9193
|
+
return this.client.agentRuns(this.repository);
|
|
9194
|
+
}
|
|
9195
|
+
/** The node-development surface of this repository. */
|
|
9196
|
+
nodeDev() {
|
|
9197
|
+
return this.client.nodeDev(this.repository);
|
|
9198
|
+
}
|
|
9199
|
+
functions() {
|
|
9200
|
+
return new HttpFunctionsApi(
|
|
9201
|
+
this.repository,
|
|
9202
|
+
(repo, name, input, options) => this.client.invokeFunction(repo, name, input, options),
|
|
9203
|
+
(repo, name, input) => this.client.invokeFunctionSync(repo, name, input)
|
|
9204
|
+
);
|
|
9205
|
+
}
|
|
9206
|
+
/**
|
|
9207
|
+
* The retrieval implementation behind {@link search} and {@link ask}.
|
|
9208
|
+
*
|
|
9209
|
+
* The SAME `SearchApi` the WebSocket client uses, handed this transport's SQL
|
|
9210
|
+
* and function runners. Retrieval is one behaviour — the query shape, the
|
|
9211
|
+
* limit clamp, the required scope, the `grounded` fail-closed mapping — and a
|
|
9212
|
+
* second copy written against HTTP would drift from the first the moment
|
|
9213
|
+
* either is touched. Only the two runners differ.
|
|
9214
|
+
*/
|
|
9215
|
+
searchApi() {
|
|
9216
|
+
if (!this._searchApi) {
|
|
9217
|
+
this._searchApi = new SearchApi(
|
|
9218
|
+
(sql, params) => this.executeSql(sql, params),
|
|
9219
|
+
async (name, input) => {
|
|
9220
|
+
const run = await this.functions().invoke(name, input, {
|
|
9221
|
+
waitForResult: true
|
|
9222
|
+
});
|
|
9223
|
+
return run?.result ?? run;
|
|
9224
|
+
}
|
|
9225
|
+
);
|
|
9226
|
+
}
|
|
9227
|
+
return this._searchApi;
|
|
9228
|
+
}
|
|
9229
|
+
/**
|
|
9230
|
+
* Find the passages matching a query, by meaning and by keyword at once.
|
|
9231
|
+
*
|
|
9232
|
+
* Available on this HTTP client as well as the WebSocket one, because this is
|
|
9233
|
+
* the client a server-side route handler reaches for — and a route handler is
|
|
9234
|
+
* exactly where retrieval belongs when the page is public: the browser must
|
|
9235
|
+
* not hold a credential that can read the repository.
|
|
9236
|
+
*
|
|
9237
|
+
* @example
|
|
9238
|
+
* ```typescript
|
|
9239
|
+
* const passages = await db.search('how much notice to terminate', {
|
|
9240
|
+
* workspaces: 'stories',
|
|
9241
|
+
* });
|
|
9242
|
+
* ```
|
|
9243
|
+
*/
|
|
9244
|
+
async search(query, options) {
|
|
9245
|
+
return this.searchApi().search(query, options);
|
|
9246
|
+
}
|
|
9247
|
+
/**
|
|
9248
|
+
* Answer a question from the stored content, with citations.
|
|
9249
|
+
*
|
|
9250
|
+
* Check `grounded` before showing the answer: when it is false nothing
|
|
9251
|
+
* relevant was retrieved and the model was never asked.
|
|
9252
|
+
*
|
|
9253
|
+
* @example
|
|
9254
|
+
* ```typescript
|
|
9255
|
+
* const { answer, citations, grounded } = await db.ask(question, {
|
|
9256
|
+
* workspaces: 'stories, handbook',
|
|
9257
|
+
* });
|
|
9258
|
+
* ```
|
|
9259
|
+
*/
|
|
9260
|
+
async ask(question, options) {
|
|
9261
|
+
return this.searchApi().ask(question, options);
|
|
9262
|
+
}
|
|
9263
|
+
/**
|
|
9264
|
+
* Get an {@link McpClient} for a content-declared MCP server on this database.
|
|
9265
|
+
*
|
|
9266
|
+
* @param slug - The `raisin:McpServer` slug (resolved in the `mcp` workspace).
|
|
9267
|
+
* @returns A client scoped to `{repo}/{branch}/{slug}`, using this database's
|
|
9268
|
+
* branch (falling back to the HTTP client's current branch).
|
|
9269
|
+
*/
|
|
9270
|
+
mcp(slug) {
|
|
9271
|
+
const transport = {
|
|
9272
|
+
rpc: (m, p) => this.client.mcpRpc(this.repository, this.schemaBranch(), slug, m, p),
|
|
9273
|
+
subscribe: (m, p) => this.client.mcpSubscribe(this.repository, this.schemaBranch(), slug, m, p)
|
|
9274
|
+
};
|
|
9275
|
+
return new McpClient(transport);
|
|
9276
|
+
}
|
|
9277
|
+
/** Effective branch for management calls. */
|
|
9278
|
+
schemaBranch() {
|
|
9279
|
+
return this.branch ?? this.client.getBranch();
|
|
9280
|
+
}
|
|
9281
|
+
/** NodeType management (get/list/getResolved/create/update/delete/publish/unpublish). */
|
|
9282
|
+
nodeTypes() {
|
|
9283
|
+
return new HttpNodeTypes(this.repository, this.schemaBranch(), this.client);
|
|
9284
|
+
}
|
|
9285
|
+
/** Archetype management (get/list/getResolved/create/update/delete/publish/unpublish). */
|
|
9286
|
+
archetypes() {
|
|
9287
|
+
return new HttpArchetypes(this.repository, this.schemaBranch(), this.client);
|
|
9288
|
+
}
|
|
9289
|
+
/** ElementType management (get/list/getResolved/create/update/delete/publish/unpublish). */
|
|
9290
|
+
elementTypes() {
|
|
9291
|
+
return new HttpElementTypes(this.repository, this.schemaBranch(), this.client);
|
|
7041
9292
|
}
|
|
7042
9293
|
};
|
|
7043
9294
|
var HttpWorkspaceClient = class {
|
|
@@ -7196,10 +9447,13 @@ var HttpWorkspaceClient = class {
|
|
|
7196
9447
|
};
|
|
7197
9448
|
|
|
7198
9449
|
// src/client.ts
|
|
7199
|
-
var
|
|
9450
|
+
var _refreshingUser;
|
|
9451
|
+
var _RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
7200
9452
|
constructor(url, options = {}) {
|
|
7201
9453
|
super();
|
|
7202
9454
|
this._currentUser = null;
|
|
9455
|
+
/** In-flight role re-resolve, used to coalesce concurrent refreshCurrentUser() calls. */
|
|
9456
|
+
__privateAdd(this, _refreshingUser, null);
|
|
7203
9457
|
// Auth state change listeners (Firebase/Supabase pattern)
|
|
7204
9458
|
this._authListeners = /* @__PURE__ */ new Set();
|
|
7205
9459
|
this._userChangeListeners = /* @__PURE__ */ new Set();
|
|
@@ -7210,23 +9464,44 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7210
9464
|
// Reconnection tracking
|
|
7211
9465
|
this._previousConnectionState = "disconnected" /* Disconnected */;
|
|
7212
9466
|
this._reconnectedListeners = /* @__PURE__ */ new Set();
|
|
9467
|
+
this._everConnected = false;
|
|
9468
|
+
// Requests queued while the connection is re-establishing.
|
|
9469
|
+
// Flushed after reconnect + re-auth; bounded by MAX_QUEUED_REQUESTS.
|
|
9470
|
+
this._queuedRequests = [];
|
|
9471
|
+
/**
|
|
9472
|
+
* Backoff delays (ms) between subscription-restore retries after reconnect.
|
|
9473
|
+
* @internal - overridable in tests
|
|
9474
|
+
*/
|
|
9475
|
+
this._restoreRetryDelaysMs = [1e3, 3e3, 9e3];
|
|
7213
9476
|
// Upload manager (uses HTTP under the hood)
|
|
7214
9477
|
this._uploadManager = null;
|
|
9478
|
+
/** In-flight refresh shared by concurrent refreshToken() callers. */
|
|
9479
|
+
this._refreshInFlight = null;
|
|
7215
9480
|
if (options.logLevel !== void 0) {
|
|
7216
9481
|
setLogLevel(options.logLevel);
|
|
7217
9482
|
}
|
|
9483
|
+
const connectionUrl = this.buildConnectionUrl(url, options.repository);
|
|
7218
9484
|
this.options = {
|
|
7219
9485
|
requestTimeout: options.requestTimeout ?? 3e4,
|
|
7220
|
-
tenantId: options.tenantId ?? this.extractTenantFromUrl(
|
|
9486
|
+
tenantId: options.tenantId ?? this.extractTenantFromUrl(connectionUrl),
|
|
7221
9487
|
defaultBranch: options.defaultBranch ?? "main"
|
|
7222
9488
|
};
|
|
7223
|
-
this.
|
|
7224
|
-
this.
|
|
9489
|
+
this._tokenRefresher = options.tokenRefresher;
|
|
9490
|
+
this._httpBaseUrl = options.httpBaseUrl ?? this.deriveHttpUrl(connectionUrl);
|
|
9491
|
+
this._repository = options.repository ?? this.extractRepositoryFromUrl(connectionUrl);
|
|
7225
9492
|
this._context = {
|
|
7226
9493
|
tenant_id: this.options.tenantId,
|
|
7227
9494
|
branch: this.options.defaultBranch
|
|
7228
9495
|
};
|
|
7229
|
-
|
|
9496
|
+
const explicitTenant = options.tenantId ?? (this.extractTenantFromUrl(url) !== "default" ? this.options.tenantId : void 0);
|
|
9497
|
+
const connectionOptions = {
|
|
9498
|
+
...options.connection,
|
|
9499
|
+
headers: {
|
|
9500
|
+
...explicitTenant ? { "x-tenant-id": explicitTenant } : {},
|
|
9501
|
+
...options.connection?.headers
|
|
9502
|
+
}
|
|
9503
|
+
};
|
|
9504
|
+
this.connection = this.createConnection(connectionUrl, connectionOptions);
|
|
7230
9505
|
this.requestTracker = new RequestTracker({
|
|
7231
9506
|
defaultTimeout: this.options.requestTimeout
|
|
7232
9507
|
});
|
|
@@ -7236,6 +9511,14 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7236
9511
|
);
|
|
7237
9512
|
this.setupConnectionHandlers();
|
|
7238
9513
|
}
|
|
9514
|
+
/**
|
|
9515
|
+
* Create the underlying WebSocket connection.
|
|
9516
|
+
* Seam for tests to inject a fake connection.
|
|
9517
|
+
* @internal
|
|
9518
|
+
*/
|
|
9519
|
+
createConnection(url, options) {
|
|
9520
|
+
return new Connection(url, options);
|
|
9521
|
+
}
|
|
7239
9522
|
/**
|
|
7240
9523
|
* Derive HTTP URL from WebSocket URL
|
|
7241
9524
|
* ws://host:port/path -> http://host:port
|
|
@@ -7251,15 +9534,45 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7251
9534
|
return "http://localhost:8081";
|
|
7252
9535
|
}
|
|
7253
9536
|
}
|
|
9537
|
+
/**
|
|
9538
|
+
* Build the WebSocket connection URL.
|
|
9539
|
+
*
|
|
9540
|
+
* When a repository option is provided and the URL carries no path
|
|
9541
|
+
* (e.g. `ws://host:8081`), build the tenant-less `/ws/{repository}`
|
|
9542
|
+
* route internally. URLs that already carry a path are used as-is.
|
|
9543
|
+
*/
|
|
9544
|
+
buildConnectionUrl(url, repository) {
|
|
9545
|
+
if (!repository) {
|
|
9546
|
+
return url;
|
|
9547
|
+
}
|
|
9548
|
+
try {
|
|
9549
|
+
const wsUrl = url.replace(/^raisin:\/\//, "ws://").replace(/^raisins:\/\//, "wss://");
|
|
9550
|
+
const parsed = new URL(wsUrl);
|
|
9551
|
+
const hasPath = parsed.pathname.split("/").some((p) => p.length > 0);
|
|
9552
|
+
if (hasPath) {
|
|
9553
|
+
return url;
|
|
9554
|
+
}
|
|
9555
|
+
return `${url.replace(/\/+$/, "")}/ws/${repository}`;
|
|
9556
|
+
} catch {
|
|
9557
|
+
return url;
|
|
9558
|
+
}
|
|
9559
|
+
}
|
|
7254
9560
|
/**
|
|
7255
9561
|
* Extract repository from URL
|
|
7256
|
-
* URL
|
|
9562
|
+
* URL forms: /ws/{repo} (tenant-less, preferred), /sys/{tenant}/{repo}
|
|
9563
|
+
* (operator/multi-tenant), /{tenant}/{repo} (legacy).
|
|
7257
9564
|
*/
|
|
7258
9565
|
extractRepositoryFromUrl(url) {
|
|
7259
9566
|
try {
|
|
7260
9567
|
const wsUrl = url.replace(/^raisin:\/\//, "ws://").replace(/^raisins:\/\//, "wss://");
|
|
7261
9568
|
const parsed = new URL(wsUrl);
|
|
7262
9569
|
const parts = parsed.pathname.split("/").filter((p) => p.length > 0);
|
|
9570
|
+
if (parts[0] === "ws") {
|
|
9571
|
+
return parts.length > 1 ? parts[1] : "";
|
|
9572
|
+
}
|
|
9573
|
+
if (parts[0] === "sys") {
|
|
9574
|
+
return parts.length > 2 ? parts[2] : "";
|
|
9575
|
+
}
|
|
7263
9576
|
return parts.length > 1 ? parts[1] : "";
|
|
7264
9577
|
} catch {
|
|
7265
9578
|
return "";
|
|
@@ -7267,14 +9580,24 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7267
9580
|
}
|
|
7268
9581
|
/**
|
|
7269
9582
|
* Extract tenant ID from URL
|
|
7270
|
-
* URL
|
|
9583
|
+
* URL forms: /ws/{repo} (tenant-less — no tenant in URL),
|
|
9584
|
+
* /sys/{tenant}/{repo} (canonical), /{tenant}/{repo} (legacy).
|
|
9585
|
+
* When the URL doesn't carry a tenant, fall back to 'default' — the
|
|
9586
|
+
* server's tenant middleware resolves it anyway (production derives the
|
|
9587
|
+
* tenant from the request itself), so an explicit tenantId is optional.
|
|
7271
9588
|
*/
|
|
7272
9589
|
extractTenantFromUrl(url) {
|
|
7273
9590
|
try {
|
|
7274
9591
|
const wsUrl = url.replace(/^raisin:\/\//, "ws://").replace(/^raisins:\/\//, "wss://");
|
|
7275
9592
|
const parsed = new URL(wsUrl);
|
|
7276
9593
|
const parts = parsed.pathname.split("/").filter((p) => p.length > 0);
|
|
7277
|
-
|
|
9594
|
+
if (parts[0] === "ws") {
|
|
9595
|
+
return "default";
|
|
9596
|
+
}
|
|
9597
|
+
if (parts[0] === "sys") {
|
|
9598
|
+
return parts.length > 1 ? parts[1] : "default";
|
|
9599
|
+
}
|
|
9600
|
+
return parts.length >= 2 ? parts[0] : "default";
|
|
7278
9601
|
} catch (error) {
|
|
7279
9602
|
return "default";
|
|
7280
9603
|
}
|
|
@@ -7288,7 +9611,11 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7288
9611
|
});
|
|
7289
9612
|
this.connection.on("stateChange", (state) => {
|
|
7290
9613
|
const wasDisconnected = this._previousConnectionState === "disconnected" /* Disconnected */ || this._previousConnectionState === "reconnecting" /* Reconnecting */;
|
|
9614
|
+
if (state === "connected" /* Connected */) {
|
|
9615
|
+
this._everConnected = true;
|
|
9616
|
+
}
|
|
7291
9617
|
if (state === "disconnected" /* Disconnected */ || state === "closed" /* Closed */) {
|
|
9618
|
+
this._queuedRequests = [];
|
|
7292
9619
|
this.requestTracker.cancelAll();
|
|
7293
9620
|
}
|
|
7294
9621
|
if (state === "connected" /* Connected */ && wasDisconnected && this._previousConnectionState !== "disconnected" /* Disconnected */) {
|
|
@@ -7319,8 +9646,13 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7319
9646
|
if (isConnectedMessage(message)) {
|
|
7320
9647
|
this.handleConnectedMessage(message);
|
|
7321
9648
|
} else if (isEventMessage(message)) {
|
|
7322
|
-
|
|
7323
|
-
|
|
9649
|
+
if (message.event_type === "permissions:changed") {
|
|
9650
|
+
logger.info("[permissions:changed] server signaled a role/permission change; re-resolving");
|
|
9651
|
+
void this.refreshCurrentUser();
|
|
9652
|
+
} else {
|
|
9653
|
+
logger.debug(`Routing to event handler`);
|
|
9654
|
+
this.eventHandler.handleEvent(message);
|
|
9655
|
+
}
|
|
7324
9656
|
} else if (isResponseEnvelope(message)) {
|
|
7325
9657
|
logger.debug(`Routing to response handler - request_id: ${message.request_id}, status: ${message.status}`);
|
|
7326
9658
|
this.handleResponse(message);
|
|
@@ -7353,19 +9685,36 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7353
9685
|
};
|
|
7354
9686
|
logger.debug(`Anonymous user info stored: ${message.user_id}`);
|
|
7355
9687
|
this._updateReadyState();
|
|
9688
|
+
this._flushQueuedRequests();
|
|
7356
9689
|
if (hasActiveSubscriptions) {
|
|
7357
9690
|
this._restoreSubscriptionsAndNotify();
|
|
7358
9691
|
}
|
|
7359
9692
|
} else {
|
|
7360
9693
|
logger.info(`[handleConnectedMessage] Stored token found, auto-authenticating...`);
|
|
7361
|
-
this.autoReauthenticate().then(async () => {
|
|
9694
|
+
this.autoReauthenticate().then(async (authenticated) => {
|
|
9695
|
+
if (authenticated) {
|
|
9696
|
+
this._flushQueuedRequests();
|
|
9697
|
+
} else {
|
|
9698
|
+
this._rejectQueuedRequests(
|
|
9699
|
+
new RaisinAuthError(
|
|
9700
|
+
"Re-authentication failed after reconnect",
|
|
9701
|
+
"AUTH_TOKEN_EXPIRED",
|
|
9702
|
+
401
|
|
9703
|
+
)
|
|
9704
|
+
);
|
|
9705
|
+
}
|
|
7362
9706
|
if (hasActiveSubscriptions) {
|
|
7363
9707
|
await this._restoreSubscriptionsAndNotify();
|
|
7364
9708
|
}
|
|
7365
9709
|
}).catch((err) => {
|
|
7366
9710
|
logger.error(`[handleConnectedMessage] Auto-authenticate failed:`, err);
|
|
9711
|
+
this._rejectQueuedRequests(
|
|
9712
|
+
err instanceof Error ? err : new Error("Re-authentication failed after reconnect")
|
|
9713
|
+
);
|
|
7367
9714
|
});
|
|
7368
9715
|
}
|
|
9716
|
+
} else {
|
|
9717
|
+
this._flushQueuedRequests();
|
|
7369
9718
|
}
|
|
7370
9719
|
this.emit("connected", {
|
|
7371
9720
|
connectionId: message.connection_id,
|
|
@@ -7374,18 +9723,41 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7374
9723
|
});
|
|
7375
9724
|
}
|
|
7376
9725
|
/**
|
|
7377
|
-
* Restore subscriptions after reconnection and notify listeners
|
|
9726
|
+
* Restore subscriptions after reconnection and notify listeners.
|
|
9727
|
+
*
|
|
9728
|
+
* Retries with backoff (see `_restoreRetryDelaysMs`); on final failure a
|
|
9729
|
+
* `subscription_restore_failed` event is emitted so applications can
|
|
9730
|
+
* resync their state (e.g. reload lists and re-subscribe manually).
|
|
7378
9731
|
* @internal
|
|
7379
9732
|
*/
|
|
7380
9733
|
async _restoreSubscriptionsAndNotify() {
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
9734
|
+
const delays = this._restoreRetryDelaysMs;
|
|
9735
|
+
let lastError;
|
|
9736
|
+
for (let attempt = 0; attempt <= delays.length; attempt++) {
|
|
9737
|
+
try {
|
|
9738
|
+
logger.info(
|
|
9739
|
+
`[_restoreSubscriptionsAndNotify] Restoring subscriptions (attempt ${attempt + 1}/${delays.length + 1})...`
|
|
9740
|
+
);
|
|
9741
|
+
await this.eventHandler.restoreSubscriptions({ retry: attempt > 0 });
|
|
9742
|
+
logger.info("[_restoreSubscriptionsAndNotify] Subscriptions restored successfully");
|
|
9743
|
+
this._emitReconnected();
|
|
9744
|
+
return;
|
|
9745
|
+
} catch (error) {
|
|
9746
|
+
lastError = error;
|
|
9747
|
+
logger.error(
|
|
9748
|
+
`[_restoreSubscriptionsAndNotify] Restore attempt ${attempt + 1} failed:`,
|
|
9749
|
+
error
|
|
9750
|
+
);
|
|
9751
|
+
if (attempt < delays.length) {
|
|
9752
|
+
await new Promise((resolve) => setTimeout(resolve, delays[attempt]));
|
|
9753
|
+
}
|
|
9754
|
+
}
|
|
7388
9755
|
}
|
|
9756
|
+
logger.error("[_restoreSubscriptionsAndNotify] Giving up after retries; events may be stale");
|
|
9757
|
+
this.emit(
|
|
9758
|
+
"subscription_restore_failed",
|
|
9759
|
+
lastError instanceof Error ? lastError : new Error(String(lastError))
|
|
9760
|
+
);
|
|
7389
9761
|
}
|
|
7390
9762
|
/**
|
|
7391
9763
|
* Emit reconnected event to all listeners
|
|
@@ -7404,13 +9776,15 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7404
9776
|
}
|
|
7405
9777
|
/**
|
|
7406
9778
|
* Auto-reauthenticate with stored JWT on reconnection
|
|
9779
|
+
*
|
|
9780
|
+
* @returns true if the session was re-authenticated, false otherwise
|
|
7407
9781
|
* @internal
|
|
7408
9782
|
*/
|
|
7409
9783
|
async autoReauthenticate() {
|
|
7410
9784
|
const token = this.authManager.storage.getAccessToken();
|
|
7411
9785
|
if (!token) {
|
|
7412
9786
|
logger.warn("[autoReauthenticate] No stored token");
|
|
7413
|
-
return;
|
|
9787
|
+
return false;
|
|
7414
9788
|
}
|
|
7415
9789
|
const payload = AuthManager.parseToken(token);
|
|
7416
9790
|
if (payload?.exp && typeof payload.exp === "number") {
|
|
@@ -7422,18 +9796,20 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7422
9796
|
this.authManager.clear();
|
|
7423
9797
|
this._currentUser = null;
|
|
7424
9798
|
this._emitAuthEvent("SESSION_EXPIRED");
|
|
7425
|
-
return;
|
|
9799
|
+
return false;
|
|
7426
9800
|
}
|
|
7427
9801
|
const newToken = this.authManager.storage.getAccessToken();
|
|
7428
9802
|
if (newToken) {
|
|
7429
9803
|
await this.authenticate({ type: "jwt", token: newToken });
|
|
9804
|
+
return true;
|
|
7430
9805
|
}
|
|
7431
|
-
return;
|
|
9806
|
+
return false;
|
|
7432
9807
|
}
|
|
7433
9808
|
}
|
|
7434
9809
|
try {
|
|
7435
9810
|
await this.authenticate({ type: "jwt", token });
|
|
7436
9811
|
logger.info("[autoReauthenticate] Successfully re-authenticated");
|
|
9812
|
+
return true;
|
|
7437
9813
|
} catch (err) {
|
|
7438
9814
|
logger.error("[autoReauthenticate] Failed to authenticate:", err);
|
|
7439
9815
|
const refreshed = await this.refreshToken();
|
|
@@ -7441,11 +9817,14 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7441
9817
|
const newToken = this.authManager.storage.getAccessToken();
|
|
7442
9818
|
if (newToken) {
|
|
7443
9819
|
await this.authenticate({ type: "jwt", token: newToken });
|
|
9820
|
+
return true;
|
|
7444
9821
|
}
|
|
9822
|
+
return false;
|
|
7445
9823
|
} else {
|
|
7446
9824
|
this.authManager.clear();
|
|
7447
9825
|
this._currentUser = null;
|
|
7448
9826
|
this._emitAuthEvent("SESSION_EXPIRED");
|
|
9827
|
+
return false;
|
|
7449
9828
|
}
|
|
7450
9829
|
}
|
|
7451
9830
|
}
|
|
@@ -7495,6 +9874,7 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7495
9874
|
async authenticate(credentials) {
|
|
7496
9875
|
if (isJwtCredentials(credentials)) {
|
|
7497
9876
|
logger.info("[authenticate] Starting JWT authentication...");
|
|
9877
|
+
this.authManager.adoptToken(credentials.token);
|
|
7498
9878
|
const payload = { token: credentials.token };
|
|
7499
9879
|
const response = await this.sendRequestInternal(
|
|
7500
9880
|
payload,
|
|
@@ -7561,18 +9941,18 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7561
9941
|
}
|
|
7562
9942
|
const url = `${this._httpBaseUrl}/auth/${repository}/login`;
|
|
7563
9943
|
logger.debug("[loginWithEmail] POST", url);
|
|
7564
|
-
const response = await
|
|
9944
|
+
const response = await this._fetchWithTimeout(url, {
|
|
7565
9945
|
method: "POST",
|
|
7566
9946
|
headers: { "Content-Type": "application/json" },
|
|
7567
9947
|
body: JSON.stringify({ email, password })
|
|
7568
9948
|
});
|
|
7569
9949
|
if (!response.ok) {
|
|
7570
9950
|
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
9951
|
+
throw new RaisinAuthError(
|
|
9952
|
+
error.message || "Login failed",
|
|
9953
|
+
error.code || "LOGIN_FAILED",
|
|
9954
|
+
response.status
|
|
9955
|
+
);
|
|
7576
9956
|
}
|
|
7577
9957
|
const tokens = await response.json();
|
|
7578
9958
|
logger.info("[loginWithEmail] HTTP login successful, user_id:", tokens.identity.id);
|
|
@@ -7601,11 +9981,11 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7601
9981
|
}
|
|
7602
9982
|
this._currentUser = {
|
|
7603
9983
|
userId: user.id,
|
|
7604
|
-
roles: [],
|
|
9984
|
+
roles: this._currentUser?.roles ?? [],
|
|
7605
9985
|
anonymous: false,
|
|
7606
9986
|
node: userNode ?? void 0
|
|
7607
9987
|
};
|
|
7608
|
-
const expiresInMs = tokens.expires_at
|
|
9988
|
+
const expiresInMs = _RaisinClient.expiresAtToMs(tokens.expires_at) - Date.now() - 5 * 60 * 1e3;
|
|
7609
9989
|
if (expiresInMs > 0) {
|
|
7610
9990
|
this.scheduleAutoRefresh(expiresInMs);
|
|
7611
9991
|
}
|
|
@@ -7629,18 +10009,18 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7629
10009
|
async registerWithEmail(email, password, repository, displayName) {
|
|
7630
10010
|
logger.info("[registerWithEmail] Starting registration...");
|
|
7631
10011
|
const url = `${this._httpBaseUrl}/auth/${repository}/register`;
|
|
7632
|
-
const response = await
|
|
10012
|
+
const response = await this._fetchWithTimeout(url, {
|
|
7633
10013
|
method: "POST",
|
|
7634
10014
|
headers: { "Content-Type": "application/json" },
|
|
7635
10015
|
body: JSON.stringify({ email, password, display_name: displayName })
|
|
7636
10016
|
});
|
|
7637
10017
|
if (!response.ok) {
|
|
7638
10018
|
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
10019
|
+
throw new RaisinAuthError(
|
|
10020
|
+
error.message || "Registration failed",
|
|
10021
|
+
error.code || "REGISTRATION_FAILED",
|
|
10022
|
+
response.status
|
|
10023
|
+
);
|
|
7644
10024
|
}
|
|
7645
10025
|
const tokens = await response.json();
|
|
7646
10026
|
logger.info("[registerWithEmail] Registration successful, user_id:", tokens.identity.id);
|
|
@@ -7667,11 +10047,11 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7667
10047
|
}
|
|
7668
10048
|
this._currentUser = {
|
|
7669
10049
|
userId: user.id,
|
|
7670
|
-
roles: [],
|
|
10050
|
+
roles: this._currentUser?.roles ?? [],
|
|
7671
10051
|
anonymous: false,
|
|
7672
10052
|
node: userNode ?? void 0
|
|
7673
10053
|
};
|
|
7674
|
-
const expiresInMs = tokens.expires_at
|
|
10054
|
+
const expiresInMs = _RaisinClient.expiresAtToMs(tokens.expires_at) - Date.now() - 5 * 60 * 1e3;
|
|
7675
10055
|
if (expiresInMs > 0) {
|
|
7676
10056
|
this.scheduleAutoRefresh(expiresInMs);
|
|
7677
10057
|
}
|
|
@@ -7763,39 +10143,18 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7763
10143
|
if (payload && payload.exp && typeof payload.exp === "number") {
|
|
7764
10144
|
if (payload.exp * 1e3 < Date.now()) {
|
|
7765
10145
|
logger.info("[initSession] Token expired, attempting refresh...");
|
|
7766
|
-
|
|
7767
|
-
if (refreshToken) {
|
|
7768
|
-
try {
|
|
7769
|
-
const url = this._repository ? `${this._httpBaseUrl}/auth/${this._repository}/refresh` : `${this._httpBaseUrl}/auth/refresh`;
|
|
7770
|
-
const response = await fetch(url, {
|
|
7771
|
-
method: "POST",
|
|
7772
|
-
headers: { "Content-Type": "application/json" },
|
|
7773
|
-
body: JSON.stringify({ refresh_token: refreshToken })
|
|
7774
|
-
});
|
|
7775
|
-
if (response.ok) {
|
|
7776
|
-
const tokens = await response.json();
|
|
7777
|
-
this.authManager.storage.setAccessToken(tokens.access_token);
|
|
7778
|
-
this.authManager.storage.setRefreshToken(tokens.refresh_token);
|
|
7779
|
-
const expiresInMs = tokens.expires_at * 1e3 - Date.now() - 5 * 60 * 1e3;
|
|
7780
|
-
if (expiresInMs > 0) {
|
|
7781
|
-
this.scheduleAutoRefresh(expiresInMs);
|
|
7782
|
-
}
|
|
7783
|
-
logger.info("[initSession] Token refreshed successfully");
|
|
7784
|
-
} else {
|
|
7785
|
-
logger.warn("[initSession] Token refresh failed, clearing session");
|
|
7786
|
-
this.authManager.clear();
|
|
7787
|
-
return null;
|
|
7788
|
-
}
|
|
7789
|
-
} catch (err) {
|
|
7790
|
-
logger.warn("[initSession] Token refresh error:", err);
|
|
7791
|
-
this.authManager.clear();
|
|
7792
|
-
return null;
|
|
7793
|
-
}
|
|
7794
|
-
} else {
|
|
10146
|
+
if (!this.authManager.storage.getRefreshToken()) {
|
|
7795
10147
|
logger.warn("[initSession] No refresh token, clearing session");
|
|
7796
10148
|
this.authManager.clear();
|
|
7797
10149
|
return null;
|
|
7798
10150
|
}
|
|
10151
|
+
const refreshed = await this.refreshToken();
|
|
10152
|
+
if (!refreshed) {
|
|
10153
|
+
logger.warn("[initSession] Token refresh failed, clearing session");
|
|
10154
|
+
this.authManager.clear();
|
|
10155
|
+
return null;
|
|
10156
|
+
}
|
|
10157
|
+
logger.info("[initSession] Token refreshed successfully");
|
|
7799
10158
|
}
|
|
7800
10159
|
}
|
|
7801
10160
|
try {
|
|
@@ -7829,7 +10188,7 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7829
10188
|
}
|
|
7830
10189
|
this._currentUser = {
|
|
7831
10190
|
userId: user.id,
|
|
7832
|
-
roles: [],
|
|
10191
|
+
roles: this._currentUser?.roles ?? [],
|
|
7833
10192
|
anonymous: false,
|
|
7834
10193
|
node: userNode ?? void 0
|
|
7835
10194
|
};
|
|
@@ -7885,6 +10244,17 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7885
10244
|
* @throws Error if no refresh token is stored or refresh fails
|
|
7886
10245
|
*/
|
|
7887
10246
|
async refreshToken() {
|
|
10247
|
+
if (this._refreshInFlight) {
|
|
10248
|
+
logger.debug("[refreshToken] Refresh already in flight, awaiting it");
|
|
10249
|
+
return this._refreshInFlight;
|
|
10250
|
+
}
|
|
10251
|
+
this._refreshInFlight = this._refreshToken().finally(() => {
|
|
10252
|
+
this._refreshInFlight = null;
|
|
10253
|
+
});
|
|
10254
|
+
return this._refreshInFlight;
|
|
10255
|
+
}
|
|
10256
|
+
async _refreshToken() {
|
|
10257
|
+
if (this._tokenRefresher) return this._refreshViaHook(this._tokenRefresher);
|
|
7888
10258
|
const refreshToken = this.authManager.storage.getRefreshToken();
|
|
7889
10259
|
if (!refreshToken) {
|
|
7890
10260
|
logger.warn("[refreshToken] No refresh token available");
|
|
@@ -7893,7 +10263,7 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7893
10263
|
try {
|
|
7894
10264
|
logger.info("[refreshToken] Refreshing access token...");
|
|
7895
10265
|
const url = this._repository ? `${this._httpBaseUrl}/auth/${this._repository}/refresh` : `${this._httpBaseUrl}/auth/refresh`;
|
|
7896
|
-
const response = await
|
|
10266
|
+
const response = await this._fetchWithTimeout(url, {
|
|
7897
10267
|
method: "POST",
|
|
7898
10268
|
headers: { "Content-Type": "application/json" },
|
|
7899
10269
|
body: JSON.stringify({ refresh_token: refreshToken })
|
|
@@ -7901,6 +10271,11 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7901
10271
|
if (!response.ok) {
|
|
7902
10272
|
const errorText = await response.text();
|
|
7903
10273
|
logger.error("[refreshToken] Refresh failed:", response.status, errorText);
|
|
10274
|
+
if (/TOKEN_REUSE_DETECTED|TOKEN_FAMILY_MISMATCH/.test(errorText)) {
|
|
10275
|
+
logger.error(
|
|
10276
|
+
"[refreshToken] Session revoked by the server as a suspected token replay. If this happened on a normal page load, two refreshes raced on the same token."
|
|
10277
|
+
);
|
|
10278
|
+
}
|
|
7904
10279
|
if (response.status === 401 || response.status === 403) {
|
|
7905
10280
|
this.authManager.clear();
|
|
7906
10281
|
this._currentUser = null;
|
|
@@ -7923,7 +10298,7 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7923
10298
|
if (this.authManager.storage instanceof LocalStorageTokenStorage) {
|
|
7924
10299
|
this.authManager.storage.setUser(user);
|
|
7925
10300
|
}
|
|
7926
|
-
const expiresInMs = tokens.expires_at
|
|
10301
|
+
const expiresInMs = _RaisinClient.expiresAtToMs(tokens.expires_at) - Date.now() - 5 * 60 * 1e3;
|
|
7927
10302
|
if (expiresInMs > 0) {
|
|
7928
10303
|
this.scheduleAutoRefresh(expiresInMs);
|
|
7929
10304
|
}
|
|
@@ -7936,6 +10311,78 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7936
10311
|
return null;
|
|
7937
10312
|
}
|
|
7938
10313
|
}
|
|
10314
|
+
/** `refreshToken()` through `ClientOptions.tokenRefresher` (the session is not the page's to renew). */
|
|
10315
|
+
async _refreshViaHook(hook) {
|
|
10316
|
+
try {
|
|
10317
|
+
const res = await hook();
|
|
10318
|
+
const accessToken = typeof res === "string" ? res : res?.accessToken;
|
|
10319
|
+
if (!accessToken) {
|
|
10320
|
+
this.authManager.clear();
|
|
10321
|
+
this._currentUser = null;
|
|
10322
|
+
this.emit("auth_error", new Error("Token refresh failed - session expired"));
|
|
10323
|
+
return null;
|
|
10324
|
+
}
|
|
10325
|
+
this.authManager.storage.setAccessToken(accessToken);
|
|
10326
|
+
this.authManager.adoptToken(accessToken);
|
|
10327
|
+
const exp = typeof res === "object" && res?.expiresAt ? _RaisinClient.expiresAtToMs(res.expiresAt) : (AuthManager.parseToken(accessToken)?.exp ?? 0) * 1e3;
|
|
10328
|
+
const inMs = exp - Date.now() - 6e4;
|
|
10329
|
+
if (exp && inMs > 0) this.scheduleAutoRefresh(inMs);
|
|
10330
|
+
const user = this._currentUser ? { id: this._currentUser.userId, email: "", displayName: null, avatarUrl: null, emailVerified: false, home: null } : { id: "", email: "", displayName: null, avatarUrl: null, emailVerified: false, home: null };
|
|
10331
|
+
this._emitAuthEvent("TOKEN_REFRESHED");
|
|
10332
|
+
this.emit("token_refreshed", user);
|
|
10333
|
+
return user;
|
|
10334
|
+
} catch (err) {
|
|
10335
|
+
logger.error("[refreshToken] tokenRefresher failed:", err);
|
|
10336
|
+
return null;
|
|
10337
|
+
}
|
|
10338
|
+
}
|
|
10339
|
+
/**
|
|
10340
|
+
* Fetch with a timeout derived from `requestTimeout`.
|
|
10341
|
+
*
|
|
10342
|
+
* Aborts the request after `requestTimeout` ms and rejects with a
|
|
10343
|
+
* `RaisinTimeoutError`. If the caller provides its own AbortSignal in
|
|
10344
|
+
* `init.signal`, it is respected: an external abort rejects with the
|
|
10345
|
+
* original AbortError instead of a timeout error.
|
|
10346
|
+
* @internal
|
|
10347
|
+
*/
|
|
10348
|
+
async _fetchWithTimeout(url, init = {}) {
|
|
10349
|
+
const timeoutMs = this.options.requestTimeout;
|
|
10350
|
+
const controller = new AbortController();
|
|
10351
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
10352
|
+
const externalSignal = init.signal ?? void 0;
|
|
10353
|
+
if (externalSignal) {
|
|
10354
|
+
if (externalSignal.aborted) {
|
|
10355
|
+
controller.abort();
|
|
10356
|
+
} else {
|
|
10357
|
+
externalSignal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
10358
|
+
}
|
|
10359
|
+
}
|
|
10360
|
+
try {
|
|
10361
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
10362
|
+
} catch (error) {
|
|
10363
|
+
if (error instanceof Error && error.name === "AbortError" && !externalSignal?.aborted) {
|
|
10364
|
+
throw new RaisinTimeoutError(
|
|
10365
|
+
`Request to ${url} timed out after ${timeoutMs}ms`,
|
|
10366
|
+
"REQUEST_TIMEOUT",
|
|
10367
|
+
timeoutMs
|
|
10368
|
+
);
|
|
10369
|
+
}
|
|
10370
|
+
throw error;
|
|
10371
|
+
} finally {
|
|
10372
|
+
clearTimeout(timer);
|
|
10373
|
+
}
|
|
10374
|
+
}
|
|
10375
|
+
/**
|
|
10376
|
+
* Normalize a token `expires_at` value to epoch milliseconds.
|
|
10377
|
+
*
|
|
10378
|
+
* Server endpoints are inconsistent: some return seconds, others
|
|
10379
|
+
* milliseconds. Anything above 1e11 can't be a seconds-epoch (year 5138),
|
|
10380
|
+
* so treat it as milliseconds.
|
|
10381
|
+
* @internal
|
|
10382
|
+
*/
|
|
10383
|
+
static expiresAtToMs(expiresAt) {
|
|
10384
|
+
return expiresAt > 1e11 ? expiresAt : expiresAt * 1e3;
|
|
10385
|
+
}
|
|
7939
10386
|
/**
|
|
7940
10387
|
* Schedule automatic token refresh
|
|
7941
10388
|
* @internal
|
|
@@ -7944,11 +10391,18 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7944
10391
|
if (this._refreshTimer) {
|
|
7945
10392
|
clearTimeout(this._refreshTimer);
|
|
7946
10393
|
}
|
|
7947
|
-
|
|
10394
|
+
const clamped = Math.min(Math.max(delayMs, 3e4), 2147483647);
|
|
10395
|
+
logger.debug(`[scheduleAutoRefresh] Scheduling refresh in ${Math.round(clamped / 1e3 / 60)} minutes`);
|
|
7948
10396
|
this._refreshTimer = setTimeout(async () => {
|
|
7949
10397
|
logger.info("[autoRefresh] Auto-refreshing token...");
|
|
7950
|
-
await this.refreshToken();
|
|
7951
|
-
|
|
10398
|
+
const user = await this.refreshToken();
|
|
10399
|
+
if (!user) {
|
|
10400
|
+
logger.error("[autoRefresh] Refresh failed, clearing session");
|
|
10401
|
+
this.authManager.clear();
|
|
10402
|
+
this._currentUser = null;
|
|
10403
|
+
this._emitAuthEvent("SESSION_EXPIRED");
|
|
10404
|
+
}
|
|
10405
|
+
}, clamped);
|
|
7952
10406
|
}
|
|
7953
10407
|
/**
|
|
7954
10408
|
* Get a database/repository interface
|
|
@@ -7967,15 +10421,35 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7967
10421
|
void 0,
|
|
7968
10422
|
() => this.getUploadManager(),
|
|
7969
10423
|
(options) => this.signAssetUrl(options),
|
|
7970
|
-
{
|
|
10424
|
+
{
|
|
10425
|
+
httpBaseUrl: this._httpBaseUrl,
|
|
10426
|
+
authManager: this.authManager,
|
|
10427
|
+
onReconnected: (callback) => this.onReconnected(callback)
|
|
10428
|
+
}
|
|
7971
10429
|
);
|
|
7972
10430
|
}
|
|
7973
10431
|
/**
|
|
7974
10432
|
* Send a request to the server
|
|
10433
|
+
*
|
|
10434
|
+
* If the connection is temporarily down (connecting/reconnecting, or
|
|
10435
|
+
* disconnected with auto-reconnect enabled), the request is queued and
|
|
10436
|
+
* flushed once the connection is re-established and re-authenticated.
|
|
10437
|
+
* Queued requests are still subject to their own request timeout, and the
|
|
10438
|
+
* queue is bounded (overflow rejects immediately).
|
|
7975
10439
|
*/
|
|
7976
10440
|
async sendRequestInternal(payload, requestType, contextOverride, requestOptions) {
|
|
7977
|
-
|
|
7978
|
-
|
|
10441
|
+
const connected = this.connection.isConnected();
|
|
10442
|
+
if (!connected) {
|
|
10443
|
+
const state = this.connection.getState();
|
|
10444
|
+
const willReconnect = state === "connecting" /* Connecting */ || state === "reconnecting" /* Reconnecting */ || state === "disconnected" /* Disconnected */ && this._everConnected && this.connection.isAutoReconnectEnabled();
|
|
10445
|
+
if (!willReconnect) {
|
|
10446
|
+
throw new Error("Not connected to server");
|
|
10447
|
+
}
|
|
10448
|
+
if (this._queuedRequests.length >= _RaisinClient.MAX_QUEUED_REQUESTS) {
|
|
10449
|
+
throw new Error(
|
|
10450
|
+
`Request queue is full (${_RaisinClient.MAX_QUEUED_REQUESTS} requests pending while reconnecting)`
|
|
10451
|
+
);
|
|
10452
|
+
}
|
|
7979
10453
|
}
|
|
7980
10454
|
const requestId = this.requestTracker.generateRequestId();
|
|
7981
10455
|
const context = contextOverride || this._context;
|
|
@@ -7994,8 +10468,15 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
7994
10468
|
try {
|
|
7995
10469
|
const encoded = encodeMessage(request);
|
|
7996
10470
|
logger.debug(`Encoded request - ID: ${requestId}, type: ${requestType}, size: ${encoded.length} bytes`);
|
|
7997
|
-
|
|
7998
|
-
|
|
10471
|
+
if (connected) {
|
|
10472
|
+
this.connection.send(encoded);
|
|
10473
|
+
logger.debug(`Request sent - ID: ${requestId}`);
|
|
10474
|
+
} else {
|
|
10475
|
+
this._queuedRequests.push({ requestId, encoded });
|
|
10476
|
+
logger.debug(
|
|
10477
|
+
`Request queued while reconnecting - ID: ${requestId}, queue size: ${this._queuedRequests.length}`
|
|
10478
|
+
);
|
|
10479
|
+
}
|
|
7999
10480
|
} catch (error) {
|
|
8000
10481
|
logger.error(`Failed to send request - ID: ${requestId}:`, error);
|
|
8001
10482
|
this.requestTracker.rejectRequest(
|
|
@@ -8006,6 +10487,42 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
8006
10487
|
}
|
|
8007
10488
|
return responsePromise;
|
|
8008
10489
|
}
|
|
10490
|
+
/**
|
|
10491
|
+
* Flush requests queued while the connection was re-establishing.
|
|
10492
|
+
* Called after the connection is connected and (re-)authenticated.
|
|
10493
|
+
* @internal
|
|
10494
|
+
*/
|
|
10495
|
+
_flushQueuedRequests() {
|
|
10496
|
+
if (this._queuedRequests.length === 0) return;
|
|
10497
|
+
const queued = this._queuedRequests;
|
|
10498
|
+
this._queuedRequests = [];
|
|
10499
|
+
logger.info(`[_flushQueuedRequests] Flushing ${queued.length} queued request(s)`);
|
|
10500
|
+
for (const { requestId, encoded } of queued) {
|
|
10501
|
+
if (!this.requestTracker.hasPendingRequest(requestId)) continue;
|
|
10502
|
+
try {
|
|
10503
|
+
this.connection.send(encoded);
|
|
10504
|
+
} catch (error) {
|
|
10505
|
+
this.requestTracker.rejectRequest(
|
|
10506
|
+
requestId,
|
|
10507
|
+
error instanceof Error ? error : new Error("Failed to send queued request")
|
|
10508
|
+
);
|
|
10509
|
+
}
|
|
10510
|
+
}
|
|
10511
|
+
}
|
|
10512
|
+
/**
|
|
10513
|
+
* Reject all requests queued while the connection was re-establishing
|
|
10514
|
+
* (e.g. re-authentication failed after reconnect).
|
|
10515
|
+
* @internal
|
|
10516
|
+
*/
|
|
10517
|
+
_rejectQueuedRequests(error) {
|
|
10518
|
+
if (this._queuedRequests.length === 0) return;
|
|
10519
|
+
const queued = this._queuedRequests;
|
|
10520
|
+
this._queuedRequests = [];
|
|
10521
|
+
logger.warn(`[_rejectQueuedRequests] Rejecting ${queued.length} queued request(s): ${error.message}`);
|
|
10522
|
+
for (const { requestId } of queued) {
|
|
10523
|
+
this.requestTracker.rejectRequest(requestId, error);
|
|
10524
|
+
}
|
|
10525
|
+
}
|
|
8009
10526
|
/**
|
|
8010
10527
|
* Get the HTTP base URL derived from the WebSocket URL
|
|
8011
10528
|
*/
|
|
@@ -8083,6 +10600,44 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
8083
10600
|
getCurrentUser() {
|
|
8084
10601
|
return this._currentUser;
|
|
8085
10602
|
}
|
|
10603
|
+
/**
|
|
10604
|
+
* Re-resolve the current user's EFFECTIVE roles from the server and refresh
|
|
10605
|
+
* `getCurrentUser()`.
|
|
10606
|
+
*
|
|
10607
|
+
* Roles are deliberately not embedded in the JWT (they are resolved live
|
|
10608
|
+
* server-side), so when an admin changes a user's roles/groups the token does
|
|
10609
|
+
* not change — the client must re-ask. This re-runs the WS JWT auth handshake
|
|
10610
|
+
* (which returns freshly resolved effective roles), updates `_currentUser`, and
|
|
10611
|
+
* emits `USER_UPDATED` so role-gated UI can react without a reload.
|
|
10612
|
+
*
|
|
10613
|
+
* Called automatically when the server pushes `permissions:changed` after an
|
|
10614
|
+
* access-control write, and exposed for on-demand refresh. Safe no-op when not
|
|
10615
|
+
* authenticated.
|
|
10616
|
+
*/
|
|
10617
|
+
async refreshCurrentUser() {
|
|
10618
|
+
if (__privateGet(this, _refreshingUser)) return __privateGet(this, _refreshingUser);
|
|
10619
|
+
const token = this.authManager.storage.getAccessToken();
|
|
10620
|
+
if (!token) return;
|
|
10621
|
+
__privateSet(this, _refreshingUser, (async () => {
|
|
10622
|
+
const before = JSON.stringify(this._currentUser?.roles ?? null);
|
|
10623
|
+
const prevNode = this._currentUser?.node;
|
|
10624
|
+
try {
|
|
10625
|
+
await this.authenticate({ type: "jwt", token });
|
|
10626
|
+
if (this._currentUser && prevNode && !this._currentUser.node) {
|
|
10627
|
+
this._currentUser.node = prevNode;
|
|
10628
|
+
}
|
|
10629
|
+
const after = JSON.stringify(this._currentUser?.roles ?? null);
|
|
10630
|
+
if (after !== before) {
|
|
10631
|
+
this._emitAuthEvent("USER_UPDATED");
|
|
10632
|
+
}
|
|
10633
|
+
} catch (err) {
|
|
10634
|
+
logger.warn("[refreshCurrentUser] role re-resolve failed:", err);
|
|
10635
|
+
} finally {
|
|
10636
|
+
__privateSet(this, _refreshingUser, null);
|
|
10637
|
+
}
|
|
10638
|
+
})());
|
|
10639
|
+
return __privateGet(this, _refreshingUser);
|
|
10640
|
+
}
|
|
8086
10641
|
/**
|
|
8087
10642
|
* Get current user ID
|
|
8088
10643
|
*
|
|
@@ -8291,7 +10846,10 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
8291
10846
|
_emitAuthEvent(event) {
|
|
8292
10847
|
const session = {
|
|
8293
10848
|
user: this.getStoredUser(),
|
|
8294
|
-
accessToken: this.authManager.storage.getAccessToken()
|
|
10849
|
+
accessToken: this.authManager.storage.getAccessToken(),
|
|
10850
|
+
// Surface effective roles so role-gated consumers can react to
|
|
10851
|
+
// SIGNED_IN / USER_UPDATED without calling getCurrentUser() themselves.
|
|
10852
|
+
roles: this._currentUser?.roles ?? null
|
|
8295
10853
|
};
|
|
8296
10854
|
const change = { event, session };
|
|
8297
10855
|
for (const callback of this._authListeners) {
|
|
@@ -8331,6 +10889,7 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
8331
10889
|
*/
|
|
8332
10890
|
async _subscribeToUserHome(repository, userHome) {
|
|
8333
10891
|
await this._unsubscribeFromUserHome();
|
|
10892
|
+
userHome = normalizeHomePath(userHome) ?? userHome;
|
|
8334
10893
|
try {
|
|
8335
10894
|
const db = this.database(repository);
|
|
8336
10895
|
const pathParts = userHome.split("/").filter((p) => p.length > 0);
|
|
@@ -8630,7 +11189,7 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
8630
11189
|
const branch = options.branch ?? this._context.branch ?? "main";
|
|
8631
11190
|
const pathWithProperty = options.propertyPath ? `${options.path}@${options.propertyPath}` : options.path;
|
|
8632
11191
|
const endpoint = `/api/repository/${options.repository}/${branch}/head/${options.workspace}${pathWithProperty}/raisin:sign`;
|
|
8633
|
-
const response = await
|
|
11192
|
+
const response = await this._fetchWithTimeout(`${this._httpBaseUrl}${endpoint}`, {
|
|
8634
11193
|
method: "POST",
|
|
8635
11194
|
headers: {
|
|
8636
11195
|
"Content-Type": "application/json",
|
|
@@ -8685,6 +11244,10 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
8685
11244
|
return _RaisinClient.forSSR(baseUrl, options);
|
|
8686
11245
|
}
|
|
8687
11246
|
};
|
|
11247
|
+
_refreshingUser = new WeakMap();
|
|
11248
|
+
/** Hard cap on requests held while reconnecting; overflow rejects immediately. */
|
|
11249
|
+
_RaisinClient.MAX_QUEUED_REQUESTS = 100;
|
|
11250
|
+
var RaisinClient = _RaisinClient;
|
|
8688
11251
|
|
|
8689
11252
|
// src/node-builder.ts
|
|
8690
11253
|
var NodeBuilder = class _NodeBuilder {
|
|
@@ -8772,7 +11335,12 @@ var NodeBuilder = class _NodeBuilder {
|
|
|
8772
11335
|
return this;
|
|
8773
11336
|
}
|
|
8774
11337
|
/**
|
|
8775
|
-
* Set the order key
|
|
11338
|
+
* Set the order key.
|
|
11339
|
+
*
|
|
11340
|
+
* @deprecated The server owns editorial ordering: the order key is assigned by
|
|
11341
|
+
* the ordering index on write, so any value set here is overwritten. To place a
|
|
11342
|
+
* node, create it (it appends to the end of its parent) then call
|
|
11343
|
+
* `nodes.reorder()`, `nodes.moveChildBefore()` or `nodes.moveChildAfter()`.
|
|
8776
11344
|
*/
|
|
8777
11345
|
orderKey(orderKey) {
|
|
8778
11346
|
this.node.order_key = orderKey;
|
|
@@ -8852,10 +11420,10 @@ var NodeBuilder = class _NodeBuilder {
|
|
|
8852
11420
|
if (!this.node.node_type) {
|
|
8853
11421
|
throw new Error("Node type is required");
|
|
8854
11422
|
}
|
|
11423
|
+
if (this.node.order_key === void 0) this.node.order_key = "";
|
|
8855
11424
|
if (!this.node.children) this.node.children = [];
|
|
8856
11425
|
if (!this.node.relations) this.node.relations = [];
|
|
8857
11426
|
if (!this.node.properties) this.node.properties = {};
|
|
8858
|
-
if (!this.node.order_key) this.node.order_key = "a";
|
|
8859
11427
|
if (typeof this.node.version !== "number") this.node.version = 1;
|
|
8860
11428
|
return this.node;
|
|
8861
11429
|
}
|
|
@@ -8937,8 +11505,8 @@ var NodeHelpers = class {
|
|
|
8937
11505
|
node_type: "MockType",
|
|
8938
11506
|
properties: {},
|
|
8939
11507
|
children: [],
|
|
11508
|
+
order_key: "",
|
|
8940
11509
|
relations: [],
|
|
8941
|
-
order_key: "a",
|
|
8942
11510
|
version: 1,
|
|
8943
11511
|
...overrides
|
|
8944
11512
|
};
|
|
@@ -9019,6 +11587,29 @@ function isTerminalEvent(event) {
|
|
|
9019
11587
|
return event.type === "flow_completed" || event.type === "flow_failed";
|
|
9020
11588
|
}
|
|
9021
11589
|
|
|
11590
|
+
// src/types/flow-definition.ts
|
|
11591
|
+
function getRefDisplayName(ref) {
|
|
11592
|
+
const path = ref["raisin:path"] || ref["raisin:ref"];
|
|
11593
|
+
const segments = path.split("/").filter(Boolean);
|
|
11594
|
+
return segments[segments.length - 1] || path;
|
|
11595
|
+
}
|
|
11596
|
+
function getRefPath(ref) {
|
|
11597
|
+
return ref["raisin:path"] || ref["raisin:ref"];
|
|
11598
|
+
}
|
|
11599
|
+
var RETRY_STRATEGIES = {
|
|
11600
|
+
none: { max_retries: 0, base_delay_ms: 0, max_delay_ms: 0 },
|
|
11601
|
+
quick: { max_retries: 3, base_delay_ms: 1e3, max_delay_ms: 1e4 },
|
|
11602
|
+
standard: { max_retries: 5, base_delay_ms: 2e3, max_delay_ms: 6e4 },
|
|
11603
|
+
aggressive: { max_retries: 10, base_delay_ms: 5e3, max_delay_ms: 12e4 },
|
|
11604
|
+
llm: { max_retries: 5, base_delay_ms: 1e4, max_delay_ms: 12e4 }
|
|
11605
|
+
};
|
|
11606
|
+
function isFlowDefinitionStep(node) {
|
|
11607
|
+
return node.node_type === "raisin:FlowStep";
|
|
11608
|
+
}
|
|
11609
|
+
function isFlowDefinitionContainer(node) {
|
|
11610
|
+
return node.node_type === "raisin:FlowContainer";
|
|
11611
|
+
}
|
|
11612
|
+
|
|
9022
11613
|
// src/utils/plan-projection.ts
|
|
9023
11614
|
function asRecord(value) {
|
|
9024
11615
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
@@ -9038,15 +11629,35 @@ function parsePlanTasks(rawTasks) {
|
|
|
9038
11629
|
}
|
|
9039
11630
|
const task = asRecord(rawTask);
|
|
9040
11631
|
if (!task) return null;
|
|
11632
|
+
const delegation = asRecord(task.delegation);
|
|
9041
11633
|
return {
|
|
9042
11634
|
taskId: asString(task.task_id) ?? asString(task.id),
|
|
9043
11635
|
title: asString(task.title) ?? `Task ${index + 1}`,
|
|
9044
11636
|
status: asString(task.status) ?? "pending",
|
|
9045
11637
|
description: asString(task.description),
|
|
9046
|
-
priority: asString(task.priority)
|
|
11638
|
+
priority: asString(task.priority),
|
|
11639
|
+
delegation: delegation ? {
|
|
11640
|
+
id: asString(delegation.id),
|
|
11641
|
+
status: asString(delegation.status) ?? "unknown",
|
|
11642
|
+
agentRef: asString(delegation.agent_ref) ?? asString(delegation.agentRef),
|
|
11643
|
+
flowInstanceId: asString(delegation.flow_instance_id) ?? asString(delegation.flowInstanceId),
|
|
11644
|
+
branch: asString(delegation.branch),
|
|
11645
|
+
baseBranch: asString(delegation.base_branch) ?? asString(delegation.baseBranch),
|
|
11646
|
+
result: delegation.result,
|
|
11647
|
+
error: asString(delegation.error)
|
|
11648
|
+
} : void 0
|
|
9047
11649
|
};
|
|
9048
11650
|
}).filter((task) => task !== null);
|
|
9049
11651
|
}
|
|
11652
|
+
var TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set(["completed", "cancelled"]);
|
|
11653
|
+
var TERMINAL_PLAN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
11654
|
+
function setPlanStatus(plan, nextStatus) {
|
|
11655
|
+
if (!nextStatus) return;
|
|
11656
|
+
if (TERMINAL_PLAN_STATUSES.has(plan.status) && !TERMINAL_PLAN_STATUSES.has(nextStatus)) {
|
|
11657
|
+
return;
|
|
11658
|
+
}
|
|
11659
|
+
plan.status = nextStatus;
|
|
11660
|
+
}
|
|
9050
11661
|
function upsertTask(plan, nextTask) {
|
|
9051
11662
|
const existingIndex = plan.tasks.findIndex(
|
|
9052
11663
|
(task) => nextTask.taskId && task.taskId === nextTask.taskId || !nextTask.taskId && task.title === nextTask.title
|
|
@@ -9055,8 +11666,12 @@ function upsertTask(plan, nextTask) {
|
|
|
9055
11666
|
plan.tasks.push(nextTask);
|
|
9056
11667
|
return;
|
|
9057
11668
|
}
|
|
11669
|
+
const existing = plan.tasks[existingIndex];
|
|
11670
|
+
if (TERMINAL_TASK_STATUSES.has(existing.status) && !TERMINAL_TASK_STATUSES.has(nextTask.status)) {
|
|
11671
|
+
return;
|
|
11672
|
+
}
|
|
9058
11673
|
plan.tasks[existingIndex] = {
|
|
9059
|
-
...
|
|
11674
|
+
...existing,
|
|
9060
11675
|
...nextTask
|
|
9061
11676
|
};
|
|
9062
11677
|
}
|
|
@@ -9100,7 +11715,7 @@ function projectPlansFromMessages(messages) {
|
|
|
9100
11715
|
plan.planPath = planPath ?? plan.planPath;
|
|
9101
11716
|
plan.planId = planId ?? plan.planId;
|
|
9102
11717
|
plan.title = asString(data.title) || plan.title;
|
|
9103
|
-
plan
|
|
11718
|
+
setPlanStatus(plan, asString(data.status));
|
|
9104
11719
|
plan.requiresApproval = Boolean(data.requires_approval) || plan.status === "pending_approval";
|
|
9105
11720
|
plan.sourceMessagePath = message.path ?? plan.sourceMessagePath;
|
|
9106
11721
|
const parsedTasks = parsePlanTasks(data.tasks);
|
|
@@ -9116,7 +11731,7 @@ function projectPlansFromMessages(messages) {
|
|
|
9116
11731
|
const plan = ensurePlan(plans, key, "Plan", timestamp);
|
|
9117
11732
|
plan.planPath = planPath ?? plan.planPath;
|
|
9118
11733
|
plan.planId = planId ?? plan.planId;
|
|
9119
|
-
plan
|
|
11734
|
+
setPlanStatus(plan, asString(data.plan_status));
|
|
9120
11735
|
plan.requiresApproval = plan.status === "pending_approval";
|
|
9121
11736
|
const taskUpdate = {
|
|
9122
11737
|
taskId: asString(data.task_id),
|
|
@@ -9137,12 +11752,28 @@ function projectPlansFromMessages(messages) {
|
|
|
9137
11752
|
plan.tasks = child.tasks.map((task) => ({
|
|
9138
11753
|
taskId: task.id,
|
|
9139
11754
|
title: task.title,
|
|
9140
|
-
status: task.status
|
|
11755
|
+
status: task.status,
|
|
11756
|
+
delegation: task.delegation ? {
|
|
11757
|
+
id: task.delegation.id,
|
|
11758
|
+
status: task.delegation.status,
|
|
11759
|
+
agentRef: task.delegation.agentRef,
|
|
11760
|
+
flowInstanceId: task.delegation.flowInstanceId,
|
|
11761
|
+
branch: task.delegation.branch,
|
|
11762
|
+
baseBranch: task.delegation.baseBranch,
|
|
11763
|
+
result: task.delegation.result,
|
|
11764
|
+
error: task.delegation.error
|
|
11765
|
+
} : void 0
|
|
9141
11766
|
}));
|
|
9142
11767
|
}
|
|
9143
11768
|
}
|
|
9144
11769
|
}
|
|
9145
11770
|
}
|
|
11771
|
+
for (const plan of plans.values()) {
|
|
11772
|
+
if (!TERMINAL_PLAN_STATUSES.has(plan.status) && plan.tasks.length > 0 && plan.tasks.every((task) => TERMINAL_TASK_STATUSES.has(task.status))) {
|
|
11773
|
+
plan.status = "completed";
|
|
11774
|
+
plan.requiresApproval = false;
|
|
11775
|
+
}
|
|
11776
|
+
}
|
|
9146
11777
|
return [...plans.values()].sort((a, b) => {
|
|
9147
11778
|
const aTime = Date.parse(a.updatedAt || "");
|
|
9148
11779
|
const bTime = Date.parse(b.updatedAt || "");
|
|
@@ -9174,6 +11805,34 @@ var ConversationStore = class {
|
|
|
9174
11805
|
this._watchdogIntervalMs = options.watchdogIntervalMs ?? 3e4;
|
|
9175
11806
|
if (this._conversationPath) {
|
|
9176
11807
|
this.ensureSubscription();
|
|
11808
|
+
} else if (this.createOptions?.anonymous) {
|
|
11809
|
+
void this.resumeAnonymous();
|
|
11810
|
+
}
|
|
11811
|
+
}
|
|
11812
|
+
anonymousOptions() {
|
|
11813
|
+
const a = this.createOptions?.anonymous;
|
|
11814
|
+
if (!a) return null;
|
|
11815
|
+
return a === true ? {} : a;
|
|
11816
|
+
}
|
|
11817
|
+
/**
|
|
11818
|
+
* Resume the visitor conversation this tab already has (the session secret
|
|
11819
|
+
* and conversation id live in sessionStorage), and load its history. A tab
|
|
11820
|
+
* without one starts nothing until the first message.
|
|
11821
|
+
*/
|
|
11822
|
+
async resumeAnonymous() {
|
|
11823
|
+
const opts = this.anonymousOptions();
|
|
11824
|
+
if (!opts || !this.createOptions || this._conversationPath) return;
|
|
11825
|
+
const agent = this.createOptions.participant;
|
|
11826
|
+
try {
|
|
11827
|
+
if (!this.manager.hasStoredAnonymous(agent, opts)) return;
|
|
11828
|
+
const convo = await this.manager.startAnonymous(agent, opts);
|
|
11829
|
+
if (this._conversationPath) return;
|
|
11830
|
+
this._conversationPath = convo.conversationPath;
|
|
11831
|
+
this._conversationType = convo.type;
|
|
11832
|
+
this.ensureSubscription();
|
|
11833
|
+
await this.loadMessages();
|
|
11834
|
+
} catch (err) {
|
|
11835
|
+
logger.debug("[ConversationStore] No visitor conversation to resume", err);
|
|
9177
11836
|
}
|
|
9178
11837
|
}
|
|
9179
11838
|
// ==========================================================================
|
|
@@ -9228,7 +11887,8 @@ var ConversationStore = class {
|
|
|
9228
11887
|
throw new Error("No conversationPath and no createOptions provided");
|
|
9229
11888
|
}
|
|
9230
11889
|
try {
|
|
9231
|
-
const
|
|
11890
|
+
const anonymous = this.anonymousOptions();
|
|
11891
|
+
const convo = anonymous ? await this.manager.startAnonymous(this.createOptions.participant, anonymous) : await this.manager.create({
|
|
9232
11892
|
participant: this.createOptions.participant,
|
|
9233
11893
|
input: this.createOptions.input
|
|
9234
11894
|
});
|
|
@@ -9357,10 +12017,12 @@ var ConversationStore = class {
|
|
|
9357
12017
|
}
|
|
9358
12018
|
switch (event.type) {
|
|
9359
12019
|
case "text_chunk":
|
|
12020
|
+
this.markStreamingActivity();
|
|
9360
12021
|
this._streamingText += event.text;
|
|
9361
12022
|
this.notify();
|
|
9362
12023
|
break;
|
|
9363
12024
|
case "thought_chunk":
|
|
12025
|
+
this.markStreamingActivity();
|
|
9364
12026
|
this.notify();
|
|
9365
12027
|
break;
|
|
9366
12028
|
case "assistant_message":
|
|
@@ -9369,6 +12031,7 @@ var ConversationStore = class {
|
|
|
9369
12031
|
this.notify();
|
|
9370
12032
|
break;
|
|
9371
12033
|
case "tool_call_started": {
|
|
12034
|
+
this.markStreamingActivity();
|
|
9372
12035
|
logger.debug("[ConversationStore] Tool started:", event.functionName);
|
|
9373
12036
|
const info = {
|
|
9374
12037
|
id: event.toolCallId,
|
|
@@ -9381,6 +12044,7 @@ var ConversationStore = class {
|
|
|
9381
12044
|
break;
|
|
9382
12045
|
}
|
|
9383
12046
|
case "tool_call_completed": {
|
|
12047
|
+
this.markStreamingActivity();
|
|
9384
12048
|
logger.debug("[ConversationStore] Tool completed:", event.toolCallId);
|
|
9385
12049
|
const idx = this._activeToolCalls.findIndex((tc) => tc.id === event.toolCallId);
|
|
9386
12050
|
if (idx >= 0) {
|
|
@@ -9455,6 +12119,7 @@ var ConversationStore = class {
|
|
|
9455
12119
|
logger.error("[ConversationStore] Turn failed", event.error);
|
|
9456
12120
|
this._error = event.error;
|
|
9457
12121
|
this._isStreaming = false;
|
|
12122
|
+
this._isWaiting = false;
|
|
9458
12123
|
this._streamingText = "";
|
|
9459
12124
|
this._activeToolCalls = [];
|
|
9460
12125
|
this.clearStreamingTimer();
|
|
@@ -9492,6 +12157,26 @@ var ConversationStore = class {
|
|
|
9492
12157
|
}
|
|
9493
12158
|
}
|
|
9494
12159
|
}
|
|
12160
|
+
/**
|
|
12161
|
+
* Agent activity observed while the store thought the turn was over.
|
|
12162
|
+
*
|
|
12163
|
+
* This happens when execution continues WITHOUT a user message — plan
|
|
12164
|
+
* approval in approve_then_auto / step_by_step (approvePlan() returns an
|
|
12165
|
+
* enqueue receipt and the agent starts executing), or agent-initiated
|
|
12166
|
+
* follow-ups. The events arrive over the persistent live subscription
|
|
12167
|
+
* (no history replay), so activity is always "now": re-enter streaming
|
|
12168
|
+
* state so UIs show progress and the end-of-turn handling (done/waiting
|
|
12169
|
+
* → deferred reload) runs again for this continuation.
|
|
12170
|
+
*/
|
|
12171
|
+
markStreamingActivity() {
|
|
12172
|
+
if (this._isStreaming) return;
|
|
12173
|
+
logger.debug("[ConversationStore] Agent activity while idle \u2014 re-entering streaming state");
|
|
12174
|
+
this._isStreaming = true;
|
|
12175
|
+
this._isWaiting = false;
|
|
12176
|
+
this._streamingText = "";
|
|
12177
|
+
this.resetStreamingTimer();
|
|
12178
|
+
this.startWatchdog();
|
|
12179
|
+
}
|
|
9495
12180
|
/**
|
|
9496
12181
|
* If accumulated streaming text hasn't been captured in an assistant_message,
|
|
9497
12182
|
* synthesize one.
|
|
@@ -9725,6 +12410,14 @@ var ConversationStore = class {
|
|
|
9725
12410
|
};
|
|
9726
12411
|
|
|
9727
12412
|
// src/stores/conversation-list-store.ts
|
|
12413
|
+
function buildChatsSubscriptionFilters(userHome) {
|
|
12414
|
+
const home = normalizeHomePath(userHome) ?? userHome;
|
|
12415
|
+
return {
|
|
12416
|
+
workspace: "raisin:access_control",
|
|
12417
|
+
path: `${home}/inbox/chats/**`,
|
|
12418
|
+
event_types: ["node:created", "node:updated"]
|
|
12419
|
+
};
|
|
12420
|
+
}
|
|
9728
12421
|
var ConversationListStore = class {
|
|
9729
12422
|
constructor(options) {
|
|
9730
12423
|
this.subscribers = /* @__PURE__ */ new Set();
|
|
@@ -9870,13 +12563,8 @@ var ConversationListStore = class {
|
|
|
9870
12563
|
const rows = result.rows;
|
|
9871
12564
|
const userHome = rows?.[0]?.home;
|
|
9872
12565
|
if (!userHome) return;
|
|
9873
|
-
const chatsPath = `${userHome}/inbox/chats`;
|
|
9874
12566
|
this._realtimeSubscription = await events.subscribe(
|
|
9875
|
-
|
|
9876
|
-
workspace: "raisin:access_control",
|
|
9877
|
-
path: chatsPath,
|
|
9878
|
-
event_types: ["node:created", "node:updated"]
|
|
9879
|
-
},
|
|
12567
|
+
buildChatsSubscriptionFilters(userHome),
|
|
9880
12568
|
async (event) => {
|
|
9881
12569
|
const eventData = event;
|
|
9882
12570
|
const nodeId = eventData.payload?.node_id;
|
|
@@ -9982,7 +12670,7 @@ function useConversation(react, options) {
|
|
|
9982
12670
|
var INITIAL_LIST_SNAPSHOT = {
|
|
9983
12671
|
conversations: [],
|
|
9984
12672
|
totalUnreadCount: 0,
|
|
9985
|
-
isLoading:
|
|
12673
|
+
isLoading: true,
|
|
9986
12674
|
error: null
|
|
9987
12675
|
};
|
|
9988
12676
|
function useConversationList(react, options) {
|
|
@@ -10860,8 +13548,164 @@ function createRaisinReact(react) {
|
|
|
10860
13548
|
useFlow: useFlow2
|
|
10861
13549
|
};
|
|
10862
13550
|
}
|
|
13551
|
+
|
|
13552
|
+
// src/integrations/vue/vue-auth.ts
|
|
13553
|
+
function createUseAuth2(vue) {
|
|
13554
|
+
return function useAuth(client) {
|
|
13555
|
+
const adapter = createAuthAdapter(client);
|
|
13556
|
+
const snapshot = vue.ref(adapter.getSnapshot());
|
|
13557
|
+
const unsubscribe = adapter.subscribe((s) => {
|
|
13558
|
+
snapshot.value = s;
|
|
13559
|
+
});
|
|
13560
|
+
vue.onUnmounted(() => {
|
|
13561
|
+
unsubscribe();
|
|
13562
|
+
adapter.destroy();
|
|
13563
|
+
});
|
|
13564
|
+
return {
|
|
13565
|
+
user: vue.computed(() => snapshot.value.user),
|
|
13566
|
+
isAuthenticated: vue.computed(() => snapshot.value.isAuthenticated),
|
|
13567
|
+
isLoading: vue.computed(() => snapshot.value.isLoading),
|
|
13568
|
+
login: adapter.login,
|
|
13569
|
+
register: adapter.register,
|
|
13570
|
+
logout: adapter.logout,
|
|
13571
|
+
initSession: adapter.initSession
|
|
13572
|
+
};
|
|
13573
|
+
};
|
|
13574
|
+
}
|
|
13575
|
+
|
|
13576
|
+
// src/integrations/vue/vue-connection.ts
|
|
13577
|
+
function createUseConnection2(vue) {
|
|
13578
|
+
return function useConnection(client) {
|
|
13579
|
+
const adapter = createConnectionAdapter(client);
|
|
13580
|
+
const snapshot = vue.ref(adapter.getSnapshot());
|
|
13581
|
+
const unsubscribe = adapter.subscribe((s) => {
|
|
13582
|
+
snapshot.value = s;
|
|
13583
|
+
});
|
|
13584
|
+
vue.onUnmounted(() => {
|
|
13585
|
+
unsubscribe();
|
|
13586
|
+
adapter.destroy();
|
|
13587
|
+
});
|
|
13588
|
+
return {
|
|
13589
|
+
state: vue.computed(() => snapshot.value.state),
|
|
13590
|
+
isConnected: vue.computed(() => snapshot.value.isConnected),
|
|
13591
|
+
isReady: vue.computed(() => snapshot.value.isReady),
|
|
13592
|
+
connect: adapter.connect,
|
|
13593
|
+
disconnect: adapter.disconnect
|
|
13594
|
+
};
|
|
13595
|
+
};
|
|
13596
|
+
}
|
|
13597
|
+
|
|
13598
|
+
// src/integrations/vue/vue-sql.ts
|
|
13599
|
+
function createUseSql2(vue) {
|
|
13600
|
+
return function useSql(database, sql, params, options) {
|
|
13601
|
+
const adapter = createSqlAdapter(database, sql, params, options);
|
|
13602
|
+
const snapshot = vue.ref(adapter.getSnapshot());
|
|
13603
|
+
const unsubscribe = adapter.subscribe((s) => {
|
|
13604
|
+
snapshot.value = s;
|
|
13605
|
+
});
|
|
13606
|
+
vue.onUnmounted(() => {
|
|
13607
|
+
unsubscribe();
|
|
13608
|
+
adapter.destroy();
|
|
13609
|
+
});
|
|
13610
|
+
return {
|
|
13611
|
+
data: vue.computed(() => snapshot.value.data),
|
|
13612
|
+
isLoading: vue.computed(() => snapshot.value.isLoading),
|
|
13613
|
+
error: vue.computed(() => snapshot.value.error),
|
|
13614
|
+
refetch: adapter.refetch
|
|
13615
|
+
};
|
|
13616
|
+
};
|
|
13617
|
+
}
|
|
13618
|
+
|
|
13619
|
+
// src/integrations/vue/vue-subscription.ts
|
|
13620
|
+
function createUseSubscription2(vue) {
|
|
13621
|
+
return function useSubscription(database, filters, callback) {
|
|
13622
|
+
const adapter = createSubscriptionAdapter(database, filters, callback);
|
|
13623
|
+
vue.onUnmounted(() => adapter.destroy());
|
|
13624
|
+
return {
|
|
13625
|
+
unsubscribe: () => adapter.destroy()
|
|
13626
|
+
};
|
|
13627
|
+
};
|
|
13628
|
+
}
|
|
13629
|
+
|
|
13630
|
+
// src/integrations/vue/vue-conversation.ts
|
|
13631
|
+
function createUseConversation(vue) {
|
|
13632
|
+
return function useConversation2(options) {
|
|
13633
|
+
const store = new ConversationStore(options);
|
|
13634
|
+
const snapshot = vue.ref(store.getSnapshot());
|
|
13635
|
+
const unsubscribe = store.subscribe((s) => {
|
|
13636
|
+
snapshot.value = s;
|
|
13637
|
+
});
|
|
13638
|
+
vue.onUnmounted(() => {
|
|
13639
|
+
unsubscribe();
|
|
13640
|
+
store.destroy();
|
|
13641
|
+
});
|
|
13642
|
+
return {
|
|
13643
|
+
conversation: vue.computed(() => snapshot.value.conversation),
|
|
13644
|
+
messages: vue.computed(() => snapshot.value.messages),
|
|
13645
|
+
isStreaming: vue.computed(() => snapshot.value.isStreaming),
|
|
13646
|
+
isWaiting: vue.computed(() => snapshot.value.isWaiting),
|
|
13647
|
+
streamingText: vue.computed(() => snapshot.value.streamingText),
|
|
13648
|
+
error: vue.computed(() => snapshot.value.error),
|
|
13649
|
+
activeToolCalls: vue.computed(() => snapshot.value.activeToolCalls),
|
|
13650
|
+
plans: vue.computed(() => snapshot.value.plans),
|
|
13651
|
+
isLoading: vue.computed(() => snapshot.value.isLoading),
|
|
13652
|
+
conversationPath: vue.computed(() => snapshot.value.conversationPath),
|
|
13653
|
+
sendMessage: (content) => store.sendMessage(content),
|
|
13654
|
+
approvePlan: async (planPath) => {
|
|
13655
|
+
await store.approvePlan(planPath);
|
|
13656
|
+
},
|
|
13657
|
+
rejectPlan: async (planPath, feedback) => {
|
|
13658
|
+
await store.rejectPlan(planPath, feedback);
|
|
13659
|
+
},
|
|
13660
|
+
stop: () => store.stop(),
|
|
13661
|
+
loadMessages: () => store.loadMessages(),
|
|
13662
|
+
store
|
|
13663
|
+
};
|
|
13664
|
+
};
|
|
13665
|
+
}
|
|
13666
|
+
function createUseConversationList(vue) {
|
|
13667
|
+
return function useConversationList2(options) {
|
|
13668
|
+
const store = new ConversationListStore(options);
|
|
13669
|
+
const snapshot = vue.ref(store.getSnapshot());
|
|
13670
|
+
const unsubscribe = store.subscribe((s) => {
|
|
13671
|
+
snapshot.value = s;
|
|
13672
|
+
});
|
|
13673
|
+
store.load().catch((err) => {
|
|
13674
|
+
logger.error("[useConversationList] Initial load failed:", err);
|
|
13675
|
+
});
|
|
13676
|
+
vue.onUnmounted(() => {
|
|
13677
|
+
unsubscribe();
|
|
13678
|
+
store.destroy();
|
|
13679
|
+
});
|
|
13680
|
+
return {
|
|
13681
|
+
conversations: vue.computed(() => snapshot.value.conversations),
|
|
13682
|
+
totalUnreadCount: vue.computed(() => snapshot.value.totalUnreadCount),
|
|
13683
|
+
isLoading: vue.computed(() => snapshot.value.isLoading),
|
|
13684
|
+
error: vue.computed(() => snapshot.value.error),
|
|
13685
|
+
createConversation: (opts) => store.createConversation(opts),
|
|
13686
|
+
deleteConversation: (conversationPath) => store.deleteConversation(conversationPath),
|
|
13687
|
+
markAsRead: (conversationPath) => store.markAsRead(conversationPath),
|
|
13688
|
+
refresh: () => store.load(),
|
|
13689
|
+
store
|
|
13690
|
+
};
|
|
13691
|
+
};
|
|
13692
|
+
}
|
|
13693
|
+
|
|
13694
|
+
// src/integrations/vue/index.ts
|
|
13695
|
+
function createRaisinVue(vue) {
|
|
13696
|
+
return {
|
|
13697
|
+
useAuth: createUseAuth2(vue),
|
|
13698
|
+
useConnection: createUseConnection2(vue),
|
|
13699
|
+
useSql: createUseSql2(vue),
|
|
13700
|
+
useSubscription: createUseSubscription2(vue),
|
|
13701
|
+
useConversation: createUseConversation(vue),
|
|
13702
|
+
useConversationList: createUseConversationList(vue)
|
|
13703
|
+
};
|
|
13704
|
+
}
|
|
10863
13705
|
// Annotate the CommonJS export names for ESM import in node:
|
|
10864
13706
|
0 && (module.exports = {
|
|
13707
|
+
AgentRunsApi,
|
|
13708
|
+
AgentRunsWsApi,
|
|
10865
13709
|
AllNodeEventTypes,
|
|
10866
13710
|
Archetypes,
|
|
10867
13711
|
AuthManager,
|
|
@@ -10881,6 +13725,7 @@ function createRaisinReact(react) {
|
|
|
10881
13725
|
DEFAULT_RETRY_BASE_DELAY,
|
|
10882
13726
|
DEFAULT_RETRY_MAX_DELAY,
|
|
10883
13727
|
DEFAULT_RETRY_OPTIONS,
|
|
13728
|
+
DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS,
|
|
10884
13729
|
Database,
|
|
10885
13730
|
ElementTypes,
|
|
10886
13731
|
EventHandler,
|
|
@@ -10888,19 +13733,27 @@ function createRaisinReact(react) {
|
|
|
10888
13733
|
FlowClient,
|
|
10889
13734
|
FlowsApi,
|
|
10890
13735
|
FunctionsApi,
|
|
13736
|
+
HttpArchetypes,
|
|
10891
13737
|
HttpDatabase,
|
|
13738
|
+
HttpElementTypes,
|
|
10892
13739
|
HttpFunctionsApi,
|
|
13740
|
+
HttpNodeTypes,
|
|
10893
13741
|
HttpWorkspaceClient,
|
|
13742
|
+
IdentityAuthApi,
|
|
13743
|
+
InboxApi,
|
|
10894
13744
|
LocalStorageTokenStorage,
|
|
10895
13745
|
LogLevel,
|
|
13746
|
+
McpClient,
|
|
10896
13747
|
MemoryTokenStorage,
|
|
10897
13748
|
NodeBuilder,
|
|
13749
|
+
NodeDevApi,
|
|
10898
13750
|
NodeEventType,
|
|
10899
13751
|
NodeFileSource,
|
|
10900
13752
|
NodeHelpers,
|
|
10901
13753
|
NodeOperations,
|
|
10902
13754
|
NodeTypes,
|
|
10903
13755
|
RAISIN_CONTEXT_KEY,
|
|
13756
|
+
RETRY_STRATEGIES,
|
|
10904
13757
|
RaisinAbortError,
|
|
10905
13758
|
RaisinAuthError,
|
|
10906
13759
|
RaisinClient,
|
|
@@ -10915,6 +13768,7 @@ function createRaisinReact(react) {
|
|
|
10915
13768
|
ResponseStatus,
|
|
10916
13769
|
RetryBuilder,
|
|
10917
13770
|
SSEClient,
|
|
13771
|
+
SchedulerApi,
|
|
10918
13772
|
SqlQuery,
|
|
10919
13773
|
Tags,
|
|
10920
13774
|
Transaction,
|
|
@@ -10922,6 +13776,7 @@ function createRaisinReact(react) {
|
|
|
10922
13776
|
UploadErrorCode,
|
|
10923
13777
|
UploadManager,
|
|
10924
13778
|
Uploader,
|
|
13779
|
+
VisitorChatError,
|
|
10925
13780
|
WorkspaceClient,
|
|
10926
13781
|
WorkspaceManager,
|
|
10927
13782
|
calculateChunkCount,
|
|
@@ -10936,31 +13791,42 @@ function createRaisinReact(react) {
|
|
|
10936
13791
|
createFlowAdapter,
|
|
10937
13792
|
createLoader,
|
|
10938
13793
|
createRaisinReact,
|
|
13794
|
+
createRaisinVue,
|
|
10939
13795
|
createRequestScopedClient,
|
|
10940
13796
|
createSSRClient,
|
|
10941
13797
|
createSqlAdapter,
|
|
10942
13798
|
createSqlHandler,
|
|
10943
13799
|
createSubscriptionAdapter,
|
|
10944
13800
|
decodeMessage,
|
|
13801
|
+
defaultVisitorSessionStore,
|
|
10945
13802
|
encodeMessage,
|
|
10946
13803
|
getChunkRange,
|
|
10947
13804
|
getContentType,
|
|
10948
13805
|
getErrorMessage,
|
|
10949
13806
|
getLogLevel,
|
|
10950
13807
|
getLoggerConfig,
|
|
13808
|
+
getRefDisplayName,
|
|
13809
|
+
getRefPath,
|
|
10951
13810
|
isEventMessage,
|
|
13811
|
+
isFlowDefinitionContainer,
|
|
13812
|
+
isFlowDefinitionStep,
|
|
10952
13813
|
isHttpClient,
|
|
10953
13814
|
isResponseEnvelope,
|
|
10954
13815
|
isRetryableError,
|
|
10955
13816
|
isTerminalEvent,
|
|
10956
13817
|
isWebSocketClient,
|
|
10957
13818
|
logger,
|
|
13819
|
+
newControlId,
|
|
13820
|
+
normalizeHomePath,
|
|
10958
13821
|
projectPlansFromMessages,
|
|
13822
|
+
readTokensFromFragment,
|
|
10959
13823
|
rowsToObjects,
|
|
10960
13824
|
setLogLevel,
|
|
10961
13825
|
sleep,
|
|
13826
|
+
subjectOf,
|
|
10962
13827
|
useConversation,
|
|
10963
13828
|
useConversationList,
|
|
10964
13829
|
useFlow,
|
|
10965
13830
|
withRetry
|
|
10966
13831
|
});
|
|
13832
|
+
//# sourceMappingURL=index.js.map
|