@jini-ai/core 0.4.1 → 0.4.3
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/CHANGELOG.md +8 -0
- package/README.md +19 -1
- package/dist/agent-tools.d.ts +1 -0
- package/dist/approval.d.ts +134 -0
- package/dist/approval.js +141 -0
- package/dist/fetch/cache.d.ts +129 -0
- package/dist/fetch/cache.js +243 -0
- package/dist/fetch/index.d.ts +5 -0
- package/dist/fetch/index.js +3 -0
- package/dist/fetch/types.d.ts +93 -0
- package/dist/fetch/types.js +2 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/model-facing-tool-errors.d.ts +152 -0
- package/dist/model-facing-tool-errors.js +124 -0
- package/dist/native-regexp.d.ts +10 -0
- package/dist/native-regexp.js +2 -0
- package/dist/primitives/index.d.ts +2 -0
- package/dist/primitives/index.js +1 -0
- package/dist/primitives/sse.d.ts +60 -0
- package/dist/primitives/sse.js +122 -0
- package/dist/redact.js +4 -3
- package/dist/registration-kit.d.ts +13 -0
- package/dist/registration-kit.js +12 -0
- package/dist/settings/agent-tools.d.ts +10 -0
- package/dist/settings/agent-tools.js +204 -0
- package/dist/settings/agent-value-write-tools.d.ts +22 -0
- package/dist/settings/agent-value-write-tools.js +157 -0
- package/dist/settings/agent-writable-preferences.d.ts +67 -0
- package/dist/settings/agent-writable-preferences.js +132 -0
- package/dist/settings/agent-write-denylist.d.ts +19 -0
- package/dist/settings/agent-write-denylist.js +15 -0
- package/dist/settings/change-feed.d.ts +83 -0
- package/dist/settings/change-feed.js +61 -0
- package/dist/settings/definitions-dispatch.d.ts +56 -0
- package/dist/settings/definitions-dispatch.js +86 -0
- package/dist/settings/dictionaries/index.d.ts +48 -0
- package/dist/settings/dictionaries/index.js +69 -0
- package/dist/settings/dictionaries/settings-dialog.ar.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ar.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.bn.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.bn.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.de.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.de.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.es.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.es.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.fa.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.fa.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.fr.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.fr.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.hi.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.hi.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.hu.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.hu.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.id.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.id.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.it.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.it.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.ja.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ja.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.ko.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ko.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.pl.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.pl.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.pt-BR.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.pt-BR.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.ru.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ru.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.th.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.th.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.tr.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.tr.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.uk.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.uk.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.ur.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ur.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.zh-CN.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.zh-CN.js +27 -0
- package/dist/settings/dictionaries/settings-dialog.zh-TW.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.zh-TW.js +27 -0
- package/dist/settings/ensure-definitions.d.ts +101 -0
- package/dist/settings/ensure-definitions.js +101 -0
- package/dist/settings/errors.d.ts +42 -0
- package/dist/settings/errors.js +46 -0
- package/dist/settings/express/cms-adapter.d.ts +20 -0
- package/dist/settings/express/cms-adapter.js +124 -0
- package/dist/settings/express/contracts.d.ts +135 -0
- package/dist/settings/express/contracts.js +2 -0
- package/dist/settings/express/http.d.ts +10 -0
- package/dist/settings/express/http.js +211 -0
- package/dist/settings/express/index.d.ts +5 -0
- package/dist/settings/express/index.js +4 -0
- package/dist/settings/express/shared.d.ts +68 -0
- package/dist/settings/express/shared.js +54 -0
- package/dist/settings/express/stream.d.ts +24 -0
- package/dist/settings/express/stream.js +147 -0
- package/dist/settings/index.d.ts +40 -0
- package/dist/settings/index.js +37 -0
- package/dist/settings/ports.d.ts +118 -0
- package/dist/settings/ports.js +2 -0
- package/dist/settings/principal-lookup.d.ts +9 -0
- package/dist/settings/principal-lookup.js +2 -0
- package/dist/settings/purge-service.d.ts +54 -0
- package/dist/settings/purge-service.js +106 -0
- package/dist/settings/repo.memory.d.ts +119 -0
- package/dist/settings/repo.memory.js +199 -0
- package/dist/settings/settings.d.ts +139 -0
- package/dist/settings/settings.js +259 -0
- package/dist/settings/tool-registrations.d.ts +54 -0
- package/dist/settings/tool-registrations.js +250 -0
- package/dist/settings/types.d.ts +125 -0
- package/dist/settings/types.js +3 -0
- package/dist/settings/ui-tab-definitions.d.ts +41 -0
- package/dist/settings/ui-tab-definitions.js +148 -0
- package/dist/settings/write-service.d.ts +320 -0
- package/dist/settings/write-service.js +683 -0
- package/dist/surface-exchanges.d.ts +209 -0
- package/dist/surface-exchanges.js +55 -0
- package/dist/text/escape.d.ts +20 -0
- package/dist/text/escape.js +23 -0
- package/dist/text/index.d.ts +1 -0
- package/dist/text/index.js +1 -0
- package/dist/tool-metadata.d.ts +71 -0
- package/dist/tool-metadata.js +56 -0
- package/dist/tool-permissions.d.ts +76 -0
- package/dist/tool-permissions.js +57 -0
- package/dist/tool-registry.d.ts +19 -1
- package/dist/tool-registry.js +44 -0
- package/package.json +79 -4
- package/plugin-directory.d.ts +32 -0
- package/plugin-directory.js +140 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ten seconds covers reading, closing and reopening a record or bouncing between screens
|
|
3
|
+
* without paying for another identical request. Five seconds was too short for that read
|
|
4
|
+
* cycle; thirty seconds exposed more other-operator staleness than the conservative initial
|
|
5
|
+
* shared default should allow. There is no default polling/focus refresh, so this bounds
|
|
6
|
+
* the extra exposure on a quick revisit. Write invalidation bypasses freshness; reads that
|
|
7
|
+
* must be fresh on every mount can choose staleTime: 0 without lowering every query default.
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_STALE_TIME = 10_000;
|
|
10
|
+
/** Minimum time an unobserved entry is kept; both adapters retain max(this, staleTime). */
|
|
11
|
+
export const IDLE_RETENTION = 5 * 60_000;
|
|
12
|
+
const browserScheduler = {
|
|
13
|
+
schedule: ({ callback, delayMs }) => setTimeout(callback, delayMs),
|
|
14
|
+
cancel: ({ handle }) => clearTimeout(handle),
|
|
15
|
+
};
|
|
16
|
+
// Normalize unknown promise rejections once so consumers receive Error without repeating
|
|
17
|
+
// the same narrowing boilerplate at every read and write call site.
|
|
18
|
+
export function toQueryError(value) {
|
|
19
|
+
return value instanceof Error ? value : new Error(typeof value === 'string' && value.trim() ? value : 'request failed');
|
|
20
|
+
}
|
|
21
|
+
/** One provider-owned cache. Requests are shared by structural primitive keys.
|
|
22
|
+
* Failed reads retain earlier data; replacement writes supersede older requests.
|
|
23
|
+
* Idle entries live for at least five minutes or their declared freshness period. */
|
|
24
|
+
export class FetchQueryCache {
|
|
25
|
+
entries = new Map();
|
|
26
|
+
disposed = false;
|
|
27
|
+
clock;
|
|
28
|
+
scheduler;
|
|
29
|
+
environment;
|
|
30
|
+
online;
|
|
31
|
+
onlineWaiters = new Set();
|
|
32
|
+
releaseEnvironment;
|
|
33
|
+
constructor(_required = {}, { clock = { nowMs: () => Date.now() }, scheduler = browserScheduler, environment } = {}) {
|
|
34
|
+
this.clock = clock;
|
|
35
|
+
this.scheduler = scheduler;
|
|
36
|
+
this.environment = environment;
|
|
37
|
+
this.online = environment?.isOnline() ?? true;
|
|
38
|
+
}
|
|
39
|
+
entry({ key }, { staleTime = DEFAULT_STALE_TIME } = {}) {
|
|
40
|
+
const hash = JSON.stringify(key);
|
|
41
|
+
let entry = this.entries.get(hash);
|
|
42
|
+
if (!entry) {
|
|
43
|
+
entry = {
|
|
44
|
+
key: [...key], state: { data: undefined, error: null, status: 'pending', isFetching: false },
|
|
45
|
+
updatedAt: 0, invalidated: false, replacement: 0, pending: undefined, pendingRead: undefined,
|
|
46
|
+
refreshAfterPending: false, listeners: new Set(), observers: new Set(),
|
|
47
|
+
retention: IDLE_RETENTION, eviction: undefined,
|
|
48
|
+
};
|
|
49
|
+
this.entries.set(hash, entry);
|
|
50
|
+
}
|
|
51
|
+
// An imperative load has no standing observer: idle eviction must not drop a value
|
|
52
|
+
// earlier than its declared freshness and unexpectedly force another network request.
|
|
53
|
+
entry.retention = Math.max(entry.retention, staleTime);
|
|
54
|
+
return entry;
|
|
55
|
+
}
|
|
56
|
+
fresh({ entry, staleTime }) {
|
|
57
|
+
return entry.state.data !== undefined && !entry.invalidated && this.clock.nowMs() - entry.updatedAt < staleTime;
|
|
58
|
+
}
|
|
59
|
+
subscribe({ entry, listener }) {
|
|
60
|
+
this.cancelEviction(entry);
|
|
61
|
+
entry.listeners.add(listener);
|
|
62
|
+
return () => { entry.listeners.delete(listener); this.scheduleEviction(entry); };
|
|
63
|
+
}
|
|
64
|
+
observe({ entry, observer }) {
|
|
65
|
+
entry.observers.add(observer);
|
|
66
|
+
return () => { entry.observers.delete(observer); this.scheduleEviction(entry); };
|
|
67
|
+
}
|
|
68
|
+
/** Cache hits respect freshness; explicit refreshes restart background reads.
|
|
69
|
+
* Initial reads still deduplicate because no earlier successful data exists to refresh. */
|
|
70
|
+
load({ entry, fetch, staleTime }, { force = false } = {}) {
|
|
71
|
+
this.cancelEviction(entry);
|
|
72
|
+
if (entry.pending && (!force || entry.state.data === undefined))
|
|
73
|
+
return entry.pending;
|
|
74
|
+
if (!force && this.fresh({ entry: entry, staleTime: staleTime })) {
|
|
75
|
+
this.scheduleEviction(entry);
|
|
76
|
+
return Promise.resolve(entry.state.data);
|
|
77
|
+
}
|
|
78
|
+
// Shared per key across loader instances: a replacement while this request is out
|
|
79
|
+
// must win over its older answer, not just protect the instance that issued the write.
|
|
80
|
+
const replacement = entry.replacement;
|
|
81
|
+
const run = () => {
|
|
82
|
+
// A queued offline refresh may have been superseded before reconnection.
|
|
83
|
+
if (entry.pending !== pending)
|
|
84
|
+
return (entry.pending ?? Promise.resolve(entry.state.data));
|
|
85
|
+
this.publish(entry, { ...entry.state, isFetching: true });
|
|
86
|
+
return fetch();
|
|
87
|
+
};
|
|
88
|
+
const pending = Promise.resolve().then(() => {
|
|
89
|
+
if (this.online)
|
|
90
|
+
return run();
|
|
91
|
+
this.publish(entry, { ...entry.state, isFetching: false });
|
|
92
|
+
return this.runOnline({ run });
|
|
93
|
+
}).then(value => {
|
|
94
|
+
const data = replacement === entry.replacement ? value : entry.state.data;
|
|
95
|
+
// Undefined is never successful query data, including an initial or background read.
|
|
96
|
+
if (data === undefined)
|
|
97
|
+
throw new Error(`${JSON.stringify(entry.key)} data is undefined`);
|
|
98
|
+
return data;
|
|
99
|
+
}).then(data => {
|
|
100
|
+
// Only the current request may publish or clear invalidation. An older transport
|
|
101
|
+
// need not support abort: its callers follow the replacement request instead.
|
|
102
|
+
if (entry.pending !== pending)
|
|
103
|
+
return (entry.pending ?? entry.state.data);
|
|
104
|
+
entry.updatedAt = this.clock.nowMs();
|
|
105
|
+
entry.invalidated = false;
|
|
106
|
+
this.publish(entry, { data, error: null, status: 'success', isFetching: false });
|
|
107
|
+
return data;
|
|
108
|
+
}, error => {
|
|
109
|
+
if (entry.pending !== pending)
|
|
110
|
+
return (entry.pending ?? entry.state.data);
|
|
111
|
+
// A replacement write is newer than this read's failure as well as its data.
|
|
112
|
+
// Keep the write's timestamp and freshness; only settle the obsolete fetch flag.
|
|
113
|
+
if (replacement !== entry.replacement) {
|
|
114
|
+
this.publish(entry, { ...entry.state, isFetching: false });
|
|
115
|
+
return entry.state.data;
|
|
116
|
+
}
|
|
117
|
+
entry.invalidated = true;
|
|
118
|
+
this.publish(entry, { ...entry.state, error: toQueryError(error), status: 'error', isFetching: false });
|
|
119
|
+
throw error;
|
|
120
|
+
}).finally(() => {
|
|
121
|
+
if (entry.pending !== pending)
|
|
122
|
+
return;
|
|
123
|
+
entry.pending = undefined;
|
|
124
|
+
entry.pendingRead = undefined;
|
|
125
|
+
if (entry.refreshAfterPending) {
|
|
126
|
+
entry.refreshAfterPending = false;
|
|
127
|
+
this.refresh(entry);
|
|
128
|
+
}
|
|
129
|
+
this.scheduleEviction(entry);
|
|
130
|
+
});
|
|
131
|
+
entry.pending = pending;
|
|
132
|
+
entry.pendingRead = { fetch, staleTime };
|
|
133
|
+
// Mark handled even when a UI caller deliberately discards the promise.
|
|
134
|
+
void pending.catch(() => { });
|
|
135
|
+
// Like TanStack's background refetches, retain the last error until success clears it
|
|
136
|
+
// or another failure replaces it. Extend that rule to retries without cached data:
|
|
137
|
+
// starting a request is not evidence of recovery; isFetching reports its progress.
|
|
138
|
+
const state = entry.state.data === undefined ? { ...entry.state, status: 'pending' } : entry.state;
|
|
139
|
+
this.publish(entry, { ...state, isFetching: this.online });
|
|
140
|
+
return pending;
|
|
141
|
+
}
|
|
142
|
+
replace({ entry, data }) {
|
|
143
|
+
entry.replacement += 1;
|
|
144
|
+
entry.updatedAt = this.clock.nowMs();
|
|
145
|
+
entry.invalidated = false;
|
|
146
|
+
this.publish(entry, { data, error: null, status: 'success', isFetching: entry.state.isFetching });
|
|
147
|
+
this.scheduleEviction(entry);
|
|
148
|
+
}
|
|
149
|
+
invalidate({ prefix }) {
|
|
150
|
+
for (const entry of this.entries.values()) {
|
|
151
|
+
if (!prefix.every((part, index) => entry.key[index] === part))
|
|
152
|
+
continue;
|
|
153
|
+
entry.invalidated = true;
|
|
154
|
+
// Revoke loader-only requests too: an active parent may read this child after
|
|
155
|
+
// the write. Its next load must start after invalidation, never join old bytes.
|
|
156
|
+
const pendingRead = entry.pendingRead;
|
|
157
|
+
entry.pending = undefined;
|
|
158
|
+
entry.pendingRead = undefined;
|
|
159
|
+
if (entry.observers.size)
|
|
160
|
+
this.refresh(entry);
|
|
161
|
+
// An awaiting loader still needs an answer even if no observer is mounted.
|
|
162
|
+
else if (pendingRead)
|
|
163
|
+
void this.load({ entry, ...pendingRead });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
onFocus() {
|
|
167
|
+
for (const entry of this.entries.values()) {
|
|
168
|
+
const observer = [...entry.observers].find(value => value.focus && !this.fresh({ entry: entry, staleTime: value.staleTime }));
|
|
169
|
+
// Environmental refreshes share a request already underway, unlike an explicit gesture.
|
|
170
|
+
if (observer && !entry.pending)
|
|
171
|
+
void this.load({ entry: entry, fetch: observer.fetch, staleTime: observer.staleTime });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** Execute a read/write once connectivity returns; failures are not retried.
|
|
175
|
+
* @param required.run Operation to invoke online.
|
|
176
|
+
* @returns The operation's original result or rejection. */
|
|
177
|
+
runOnline({ run }) {
|
|
178
|
+
if (this.online)
|
|
179
|
+
return Promise.resolve().then(() => {
|
|
180
|
+
// Connectivity may change between queuing the operation and its invocation.
|
|
181
|
+
if (!this.online)
|
|
182
|
+
return this.runOnline({ run });
|
|
183
|
+
return run();
|
|
184
|
+
});
|
|
185
|
+
return new Promise(resolve => { this.onlineWaiters.add(resolve); }).then(() => this.runOnline({ run }));
|
|
186
|
+
}
|
|
187
|
+
/** Stop timers on provider disposal; reactivate for React's effect replay. */
|
|
188
|
+
activate() {
|
|
189
|
+
this.disposed = false;
|
|
190
|
+
if (!this.environment || this.releaseEnvironment)
|
|
191
|
+
return;
|
|
192
|
+
const releaseOnline = this.environment.subscribeOnline({ listener: online => this.onOnline({ online }) });
|
|
193
|
+
const releaseFocus = this.environment.subscribeFocus({ listener: () => this.onFocus() });
|
|
194
|
+
this.releaseEnvironment = () => { releaseOnline(); releaseFocus(); };
|
|
195
|
+
this.onOnline({ online: this.environment.isOnline() });
|
|
196
|
+
}
|
|
197
|
+
dispose() {
|
|
198
|
+
this.disposed = true;
|
|
199
|
+
this.releaseEnvironment?.();
|
|
200
|
+
this.releaseEnvironment = undefined;
|
|
201
|
+
for (const entry of this.entries.values())
|
|
202
|
+
this.cancelEviction(entry);
|
|
203
|
+
}
|
|
204
|
+
onOnline({ online }) {
|
|
205
|
+
const reconnect = online && !this.online;
|
|
206
|
+
this.online = online;
|
|
207
|
+
if (!online)
|
|
208
|
+
return;
|
|
209
|
+
for (const resume of this.onlineWaiters)
|
|
210
|
+
resume();
|
|
211
|
+
this.onlineWaiters.clear();
|
|
212
|
+
if (!reconnect)
|
|
213
|
+
return;
|
|
214
|
+
for (const entry of this.entries.values()) {
|
|
215
|
+
const observer = [...entry.observers].find(value => !this.fresh({ entry, staleTime: value.staleTime }));
|
|
216
|
+
// A paused request already owns its resumption; restarting it here would double-fetch.
|
|
217
|
+
if (observer && !entry.pending)
|
|
218
|
+
void this.load({ entry, fetch: observer.fetch, staleTime: observer.staleTime });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
refresh(entry) {
|
|
222
|
+
const observer = entry.observers.values().next().value;
|
|
223
|
+
if (observer)
|
|
224
|
+
void this.load({ entry: entry, fetch: observer.fetch, staleTime: observer.staleTime }, { force: true });
|
|
225
|
+
}
|
|
226
|
+
publish(entry, state) {
|
|
227
|
+
entry.state = state;
|
|
228
|
+
for (const listener of entry.listeners)
|
|
229
|
+
listener();
|
|
230
|
+
}
|
|
231
|
+
cancelEviction(entry) {
|
|
232
|
+
if (entry.eviction !== undefined)
|
|
233
|
+
this.scheduler.cancel({ handle: entry.eviction });
|
|
234
|
+
entry.eviction = undefined;
|
|
235
|
+
}
|
|
236
|
+
scheduleEviction(entry) {
|
|
237
|
+
this.cancelEviction(entry);
|
|
238
|
+
if (this.disposed || entry.listeners.size || entry.observers.size || entry.pending || !Number.isFinite(entry.retention))
|
|
239
|
+
return;
|
|
240
|
+
entry.eviction = this.scheduler.schedule({ callback: () => this.entries.delete(JSON.stringify(entry.key)), delayMs: entry.retention });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Framework-neutral query contracts and cache; hosts supply transport callbacks and environment ports. */
|
|
2
|
+
export { DEFAULT_STALE_TIME, IDLE_RETENTION, FetchQueryCache, toQueryError } from './cache.js';
|
|
3
|
+
export type { QueryCacheSchedulerPort, FetchQueryEnvironmentPort, FetchQueryCacheOptions, QueryState, QueryObserver, CacheEntry } from './cache.js';
|
|
4
|
+
export type { QueryKey, QueryStatus, QueryResult, FetchQueryOptions, MutationStatus, MutationResult, FetchMutationOptions, CachedLoaderOptions, CachedLoader, FetchQueryAdapter } from './types.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep the contract independent of any query library so an adapter swap does not require
|
|
3
|
+
* rewriting callers. Add narrow required capabilities here rather than exporting library
|
|
4
|
+
* result/client types that would bypass this boundary.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Keys contain primitive array parts so parameterised resources compose without fragile
|
|
8
|
+
* string formatting or object-identity mismatches. Invalidation matches by PREFIX: a list
|
|
9
|
+
* key also refreshes its derived child keys. Exact-key equality would compile while quietly
|
|
10
|
+
* breaking that guarantee; avoid a shared prefix for resources that merely sound related,
|
|
11
|
+
* or writes will fan out unnecessary refetches.
|
|
12
|
+
* Stay primitive-only: the built-in cache matches each part with === while TanStack deep-matches,
|
|
13
|
+
* so an object part would invalidate under one adapter and silently not under the other.
|
|
14
|
+
*/
|
|
15
|
+
export type QueryKey = readonly (string | number)[];
|
|
16
|
+
export type QueryStatus = "loading" | "success" | "error";
|
|
17
|
+
export interface QueryResult<T> {
|
|
18
|
+
data: T | undefined;
|
|
19
|
+
error: Error | null;
|
|
20
|
+
status: QueryStatus;
|
|
21
|
+
isFetching: boolean;
|
|
22
|
+
refetch: () => void;
|
|
23
|
+
}
|
|
24
|
+
export interface FetchQueryOptions<T> {
|
|
25
|
+
key: QueryKey;
|
|
26
|
+
fetch: () => Promise<T>;
|
|
27
|
+
/**
|
|
28
|
+
* Gate on user intent or prerequisites to avoid fetching an undefined id. Disabling does
|
|
29
|
+
* not erase cached failures: without any successful data, report loading/error:null so
|
|
30
|
+
* a gesture-gated read cannot show an old failure before the user asks for it. With
|
|
31
|
+
* earlier data, report success but retain a later background error so known-stale data
|
|
32
|
+
* need not be presented as an unqualified success.
|
|
33
|
+
*/
|
|
34
|
+
enabled?: boolean;
|
|
35
|
+
staleTime?: number;
|
|
36
|
+
refetchOnWindowFocus?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export type MutationStatus = "idle" | "pending" | "success" | "error";
|
|
39
|
+
export interface MutationResult<TInput, TOutput> {
|
|
40
|
+
/**
|
|
41
|
+
* Awaiting the write receives its result or rejection; discarding the same promise and
|
|
42
|
+
* reading state is safe because it already has a rejection handler. Returning a bare
|
|
43
|
+
* rejecting promise would make only the awaiting form safe. Node unhandledRejection
|
|
44
|
+
* can verify this contract even where a DOM unhandledrejection event does not fire.
|
|
45
|
+
*/
|
|
46
|
+
mutate: (required: {
|
|
47
|
+
input: TInput;
|
|
48
|
+
}) => Promise<TOutput>;
|
|
49
|
+
status: MutationStatus;
|
|
50
|
+
error: Error | null;
|
|
51
|
+
reset: () => void;
|
|
52
|
+
}
|
|
53
|
+
export interface FetchMutationOptions<TInput, TOutput> {
|
|
54
|
+
run: (required: {
|
|
55
|
+
input: TInput;
|
|
56
|
+
}) => Promise<TOutput>;
|
|
57
|
+
invalidates?: readonly QueryKey[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Keep key and fetch references stable: they are memo dependencies of the loader handle.
|
|
61
|
+
* Failed fetches reject and are not cached, so the next load retries instead of replaying
|
|
62
|
+
* a failure as data. Retain an idle value at least as long as its declared freshness: an
|
|
63
|
+
* imperative loader has no persistent observer to keep it alive.
|
|
64
|
+
*/
|
|
65
|
+
export interface CachedLoaderOptions<T> {
|
|
66
|
+
key: QueryKey;
|
|
67
|
+
fetch: () => Promise<T>;
|
|
68
|
+
staleTime?: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* A plain promise loader serves components that own their loading UI rather than consuming
|
|
72
|
+
* a hook result. It shares the provider cache, including one in-flight request per key.
|
|
73
|
+
*/
|
|
74
|
+
export interface CachedLoader<T> {
|
|
75
|
+
peek: () => T | undefined;
|
|
76
|
+
load: (required?: Pick<CachedLoaderOptions<T>, "key" | "fetch">) => Promise<T>;
|
|
77
|
+
replace: (required: {
|
|
78
|
+
value: T;
|
|
79
|
+
}) => void;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Keep the provider-facing hooks, loader and invalidator behind one binding so replacing
|
|
83
|
+
* the cache implementation does not leave different consumers on different adapters.
|
|
84
|
+
*/
|
|
85
|
+
export interface FetchQueryAdapter {
|
|
86
|
+
useFetchQuery: <T>(required: Pick<FetchQueryOptions<T>, "key" | "fetch">, optional?: Omit<FetchQueryOptions<T>, "key" | "fetch">) => QueryResult<T>;
|
|
87
|
+
useFetchMutation: <TInput, TOutput>(required: Pick<FetchMutationOptions<TInput, TOutput>, "run">, optional?: Omit<FetchMutationOptions<TInput, TOutput>, "run">) => MutationResult<TInput, TOutput>;
|
|
88
|
+
useInvalidate: () => (required: {
|
|
89
|
+
key: QueryKey;
|
|
90
|
+
}) => void;
|
|
91
|
+
useCachedLoader: <T>(required: Pick<CachedLoaderOptions<T>, "key" | "fetch">, optional?: Omit<CachedLoaderOptions<T>, "key" | "fetch">) => CachedLoader<T>;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=types.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './token.js';
|
|
2
|
+
export * from './tool-metadata.js';
|
|
2
3
|
export * from './pack.js';
|
|
3
4
|
export * from './bindings.js';
|
|
4
5
|
export type { Daemon, DaemonConfig, DaemonOptions } from './daemon.js';
|
|
@@ -9,8 +10,9 @@ export * from './redact.js';
|
|
|
9
10
|
export * from './api-token-auth.js';
|
|
10
11
|
export * from './origin-validation.js';
|
|
11
12
|
export * from './principal.js';
|
|
13
|
+
export * from './surface-exchanges.js';
|
|
12
14
|
export type { AuthorizationDecision, RunRef, SurfaceEmission, SurfaceEmitter, ToolAuthorizationContext, ToolDescriptor, ToolExecutionContext, ToolExecutionOptions, ToolHandler, ToolPolicy, ToolRegistration, ToolRegistry, } from './tool-registry.js';
|
|
13
|
-
export { createToolRegistry, isReadOnlyTool, ToolInputError } from './tool-registry.js';
|
|
15
|
+
export { createToolRegistrationScope, toolRegistryRevision, createToolRegistry, isReadOnlyTool, ToolInputError } from './tool-registry.js';
|
|
14
16
|
export * from './tool-tokens.js';
|
|
15
17
|
export * from './gated-mutations.js';
|
|
16
18
|
export * from './model-facing-tool-errors.js';
|
|
@@ -18,4 +20,7 @@ export * from './contribution-registry.js';
|
|
|
18
20
|
export * from './naming.js';
|
|
19
21
|
export type { AgentToolSideEffect, AgentToolActorClassRule, AgentToolDefinition } from './agent-tools.js';
|
|
20
22
|
export * from "./registration-kit.js";
|
|
23
|
+
export * from "./approval.js";
|
|
24
|
+
export * from "./native-regexp.js";
|
|
25
|
+
export { ForbiddenError as ToolPermissionDeniedError, requireToolPermission, adaptLegacyAuthorize, type LegacyToolAuthorizeFn, type AuthorizationPort as ToolPermissionAuthorizationPort, type RequireToolPermissionRequired, type RequireToolPermissionOptional, } from "./tool-permissions.js";
|
|
21
26
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './token.js';
|
|
2
|
+
export * from './tool-metadata.js';
|
|
2
3
|
export * from './pack.js';
|
|
3
4
|
export * from './bindings.js';
|
|
4
5
|
export { createDaemon } from './daemon.js';
|
|
@@ -7,11 +8,15 @@ export * from './redact.js';
|
|
|
7
8
|
export * from './api-token-auth.js';
|
|
8
9
|
export * from './origin-validation.js';
|
|
9
10
|
export * from './principal.js';
|
|
10
|
-
export
|
|
11
|
+
export * from './surface-exchanges.js';
|
|
12
|
+
export { createToolRegistrationScope, toolRegistryRevision, createToolRegistry, isReadOnlyTool, ToolInputError } from './tool-registry.js';
|
|
11
13
|
export * from './tool-tokens.js';
|
|
12
14
|
export * from './gated-mutations.js';
|
|
13
15
|
export * from './model-facing-tool-errors.js';
|
|
14
16
|
export * from './contribution-registry.js';
|
|
15
17
|
export * from './naming.js';
|
|
16
18
|
export * from "./registration-kit.js";
|
|
19
|
+
export * from "./approval.js";
|
|
20
|
+
export * from "./native-regexp.js";
|
|
21
|
+
export { ForbiddenError as ToolPermissionDeniedError, requireToolPermission, adaptLegacyAuthorize, } from "./tool-permissions.js";
|
|
17
22
|
//# sourceMappingURL=index.js.map
|
|
@@ -3,35 +3,187 @@
|
|
|
3
3
|
* Rule evaluation is O(r); container wrapping O(n), with no I/O or reporting dependencies.
|
|
4
4
|
*/
|
|
5
5
|
import { type ToolHandler, type ToolRegistration } from "./tool-registry.js";
|
|
6
|
+
/**
|
|
7
|
+
* One entry in a domain's allowlist: an error class, and the stable code prefix its message is
|
|
8
|
+
* published to the model under.
|
|
9
|
+
*
|
|
10
|
+
* The code is prepended before a `: ` separator (`MEMBERS_NOT_FOUND: member 'x' was not found`) so
|
|
11
|
+
* a model — and a test — can match on a stable token without parsing prose, mirroring the prefix
|
|
12
|
+
* convention `features/widgets/tool-registrations.ts` already shipped.
|
|
13
|
+
*
|
|
14
|
+
* `guidance`, when present, is appended after the message. Use it only for a failure where the
|
|
15
|
+
* caller's next action is not obvious from the message alone (a version conflict that needs a
|
|
16
|
+
* re-read, say); a not-found needs no coaching.
|
|
17
|
+
*/
|
|
6
18
|
export interface ModelFacingErrorRule {
|
|
19
|
+
/** The class to match with `instanceof`. Declared to accept any constructor shape so a domain can
|
|
20
|
+
* list an error class whose constructor takes extra fields. */
|
|
7
21
|
readonly error: abstract new (...args: never[]) => Error;
|
|
22
|
+
/** The stable code published before `: `. Conventionally `<DOMAIN>_<REASON>`, SCREAMING_SNAKE. */
|
|
8
23
|
readonly code: string;
|
|
24
|
+
/** Published INSTEAD of the error's own message. Set it for a class whose KIND is the actionable
|
|
25
|
+
* reason a caller needs but whose TEXT is not safe to publish — one that wraps another
|
|
26
|
+
* component's error (a sealer's, a driver's, a keyring's), which can carry plaintext, an internal
|
|
27
|
+
* address, an env var name or an absolute path. Omit it to publish the class's own message
|
|
28
|
+
* verbatim, which is only correct when every construction site builds that message from fixed
|
|
29
|
+
* text and the caller's own input.
|
|
30
|
+
*
|
|
31
|
+
* The same field, for the same reason, as {@link CallerSafeErrorRule.message} — these two
|
|
32
|
+
* allowlists are the RETURNED-result and THROWN-rejection halves of one policy, and a class whose
|
|
33
|
+
* kind is safe to name on one half is safe to name on the other. They drifted until 2026-09-18:
|
|
34
|
+
* `features/custom-credentials` listed `CustomCredentialSecretStoreUnconfiguredError` with a fixed
|
|
35
|
+
* message in its `CallerSafeErrorRule` allowlist, but could not list it here at all, because the
|
|
36
|
+
* only thing this shape could publish was the very text that had to stay in. A missing site root
|
|
37
|
+
* key — the one operator-fixable failure in that domain — therefore reached the model as a bare
|
|
38
|
+
* `INTERNAL_ERROR` on every credential-using tool. */
|
|
9
39
|
readonly message?: string;
|
|
40
|
+
/** Optional recovery guidance appended after the message. */
|
|
10
41
|
readonly guidance?: string;
|
|
11
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* The code every domain publishes an authorization refusal under, parameterized by domain prefix.
|
|
45
|
+
*
|
|
46
|
+
* Exists because `ForbiddenError` is the ONE class in this ladder that no domain declares: the kit's
|
|
47
|
+
* `requireToolPermission` throws it on every domain's behalf. Deriving the rule here rather than
|
|
48
|
+
* retyping `{ error: ForbiddenError, code: "X_FORBIDDEN" }` in six files means a later decision to
|
|
49
|
+
* reconcile this code with the HTTP arm's own `FORBIDDEN` is a one-line change here, not a sweep.
|
|
50
|
+
*
|
|
51
|
+
* NOTE (2026-09-16): the model-facing and HTTP arms disagree on this code today — the HTTP mappers
|
|
52
|
+
* return `FORBIDDEN` while the model-facing arm returns `<DOMAIN>_FORBIDDEN`. That drift predates
|
|
53
|
+
* this file (see `features/widgets/tool-registrations.ts`'s `toModelFacingWidgetsError` doc, which
|
|
54
|
+
* documented it for Widgets) and is owned elsewhere. This helper deliberately matches the SHIPPED
|
|
55
|
+
* model-facing convention rather than inventing a third spelling.
|
|
56
|
+
*
|
|
57
|
+
* The refusal message `requireToolPermission` builds names the principal, the permission, and the
|
|
58
|
+
* `authorize()` reason — no data, no internals. Surfacing it tells a model whether to stop asking
|
|
59
|
+
* or to ask a human for access; redacting it tells it only that something broke.
|
|
60
|
+
*
|
|
61
|
+
* @param required.domainPrefix - The domain's SCREAMING_SNAKE prefix, e.g. `MEMBERS`.
|
|
62
|
+
* @param required.error - The consumer's permission-error class; core has no CMS dependency.
|
|
63
|
+
* @returns The rule to place in that domain's allowlist.
|
|
64
|
+
* @complexity O(1).
|
|
65
|
+
*/
|
|
12
66
|
export declare function forbiddenRule(required: {
|
|
13
67
|
domainPrefix: string;
|
|
14
68
|
error: ModelFacingErrorRule["error"];
|
|
15
69
|
}): ModelFacingErrorRule;
|
|
70
|
+
/**
|
|
71
|
+
* Re-classifies one rejection against a domain's allowlist, so a listed domain error reaches the
|
|
72
|
+
* model as a `ToolInputError` carrying its real message instead of a redacted `INTERNAL_ERROR`.
|
|
73
|
+
*
|
|
74
|
+
* Rules are evaluated IN ORDER and the first `instanceof` match wins, so a domain listing both a
|
|
75
|
+
* subclass and its superclass must place the subclass first — the same ordering discipline any
|
|
76
|
+
* hand-written `instanceof` ladder carries, made explicit here because an array hides it less than
|
|
77
|
+
* a chain of `if`s does.
|
|
78
|
+
*
|
|
79
|
+
* A matched rule publishes the error's own message, or {@link ModelFacingErrorRule.message} instead
|
|
80
|
+
* when the rule sets one — see that field for which classes need it and why.
|
|
81
|
+
*
|
|
82
|
+
* An already-`ToolInputError` rejection is returned untouched: it is already correctly classified,
|
|
83
|
+
* and re-wrapping it would double a code prefix that a `withSchemaOnRejection` wrap may have
|
|
84
|
+
* already attached.
|
|
85
|
+
*
|
|
86
|
+
* @param required.err - The rejection, of unknown type — anything a handler can throw.
|
|
87
|
+
* @param required.rules - The domain's allowlist. Anything unmatched is returned UNCHANGED and stays
|
|
88
|
+
* redacted; see this file's header for why that default is the security-relevant half.
|
|
89
|
+
* @returns The value to re-throw: a `ToolInputError` for a matched rule, otherwise `err` itself.
|
|
90
|
+
* @complexity O(r) in the rule count, on the failure path only.
|
|
91
|
+
*/
|
|
16
92
|
export declare function reclassifyToolError(required: {
|
|
17
93
|
err: unknown;
|
|
18
94
|
rules: readonly ModelFacingErrorRule[];
|
|
19
95
|
}): unknown;
|
|
96
|
+
/**
|
|
97
|
+
* Wraps EVERY handler in a domain's map with {@link reclassifyToolError}, returning a new map.
|
|
98
|
+
*
|
|
99
|
+
* Wrapping the whole map once — rather than reclassifying at each call site — is the point. This
|
|
100
|
+
* codebase's dominant defect is a correct primitive with an unwired call site, and a per-call-site
|
|
101
|
+
* reshape is exactly that defect waiting to happen: `features/post/tool-registrations.ts` had a
|
|
102
|
+
* correct `toModelFacingUpdateError` wired into ONE of its handlers while its siblings kept
|
|
103
|
+
* throwing the same class straight through to the redactor. A map-level wrap cannot have a missed
|
|
104
|
+
* arm, because it has no arms.
|
|
105
|
+
*
|
|
106
|
+
* The wrap is transparent on the success path: the handler's own return value is passed through
|
|
107
|
+
* untouched, and only the `catch` does work.
|
|
108
|
+
*
|
|
109
|
+
* @param required.handlers - The domain's handler map, keyed by tool id.
|
|
110
|
+
* @param rules - The domain's allowlist, applied identically to every handler.
|
|
111
|
+
* @returns A new map with the same keys; the input map is not mutated.
|
|
112
|
+
* @complexity O(h) handlers wrapped once at build time; O(r) per failed call, none per successful one.
|
|
113
|
+
*/
|
|
20
114
|
export declare function withModelFacingErrors(required: {
|
|
21
115
|
handlers: Readonly<Record<string, ToolHandler>>;
|
|
22
116
|
rules: readonly ModelFacingErrorRule[];
|
|
23
117
|
}): Record<string, ToolHandler>;
|
|
118
|
+
/**
|
|
119
|
+
* Wraps EVERY registration's handler in a domain's `ToolRegistration[]` with
|
|
120
|
+
* {@link reclassifyToolError}, returning a new array. The `ToolRegistration[]`-shaped sibling of
|
|
121
|
+
* {@link withModelFacingErrors} — for the registry-converted domains whose `build*Registrations`
|
|
122
|
+
* returns an array of `{descriptor, handler, policy}` (see `assistant/tool-contribution-registry.ts`'s
|
|
123
|
+
* `ToolContributor.build`) rather than a `Record<toolId, handler>`. `features/entries`,
|
|
124
|
+
* `features/content-types`, `features/navigation` and `features/workspace` are re-exported Jini
|
|
125
|
+
* builders of exactly this shape — see each one's `contribute*Tools`.
|
|
126
|
+
*
|
|
127
|
+
* Same transparency guarantee as {@link withModelFacingErrors}: only the `catch` arm does work, and
|
|
128
|
+
* every other field of each registration (`descriptor`, `policy`) passes through unchanged.
|
|
129
|
+
*
|
|
130
|
+
* @param required.registrations - The domain's registrations, in whatever order `build*Registrations`
|
|
131
|
+
* returned them.
|
|
132
|
+
* @param rules - The domain's allowlist, applied identically to every registration.
|
|
133
|
+
* @returns A new array with the same length and order; the input array is not mutated.
|
|
134
|
+
* @complexity O(n) registrations wrapped once at build time; O(r) per failed call, none per successful one.
|
|
135
|
+
*/
|
|
24
136
|
export declare function withModelFacingRegistrationErrors(required: {
|
|
25
137
|
registrations: readonly ToolRegistration[];
|
|
26
138
|
rules: readonly ModelFacingErrorRule[];
|
|
27
139
|
}): ToolRegistration[];
|
|
140
|
+
/**
|
|
141
|
+
* One entry in a structured-result allowlist — see {@link callerSafeErrorMessage}.
|
|
142
|
+
*
|
|
143
|
+
* No `code`: a structured result already carries its own `reason` discriminant, and its message is
|
|
144
|
+
* often rendered to a human as-is, so no prefix is added.
|
|
145
|
+
*/
|
|
28
146
|
export interface CallerSafeErrorRule {
|
|
147
|
+
/** The class to match with `instanceof`. Same constructor shape as {@link ModelFacingErrorRule.error}. */
|
|
29
148
|
readonly error: abstract new (...args: never[]) => Error;
|
|
149
|
+
/** Published INSTEAD of the error's own message. Set it for a class whose KIND is safe and worth
|
|
150
|
+
* naming but whose message is not — one that embeds another error's text. Omit it to publish the
|
|
151
|
+
* class's own message verbatim, which is only correct when every construction site builds that
|
|
152
|
+
* message from fixed text and the caller's own input. */
|
|
30
153
|
readonly message?: string;
|
|
31
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* The message a RETURNED failure result may carry for `err`: a listed class's message (or its fixed
|
|
157
|
+
* replacement), otherwise `fallback`.
|
|
158
|
+
*
|
|
159
|
+
* Unknown means redacted. An error class added tomorrow, a raw driver error, or a non-`Error` throw
|
|
160
|
+
* all get `fallback`, never their own text. Rules are evaluated in order; the first `instanceof`
|
|
161
|
+
* match wins.
|
|
162
|
+
*
|
|
163
|
+
* @param required.err - The caught value, of unknown type.
|
|
164
|
+
* @param required.rules - The allowlist.
|
|
165
|
+
* @param required.fallback - Fixed text for everything unlisted. Must not be built from `err`.
|
|
166
|
+
* @returns The message to publish.
|
|
167
|
+
* @complexity O(r) in the rule count.
|
|
168
|
+
*/
|
|
32
169
|
export declare function callerSafeErrorMessage(required: {
|
|
33
170
|
err: unknown;
|
|
34
171
|
rules: readonly CallerSafeErrorRule[];
|
|
35
172
|
fallback: string;
|
|
36
173
|
}): string;
|
|
174
|
+
/**
|
|
175
|
+
* What a server-side log line may say about an error whose message is NOT known to be safe: its class
|
|
176
|
+
* name, a constant-token `code`, and the same for its direct `cause`. Never the message, which can
|
|
177
|
+
* quote a secret — `JSON.parse` quotes its input, and a sealer is handed plaintext.
|
|
178
|
+
*
|
|
179
|
+
* Use it wherever the raw message could carry a credential. Where a class's message is designed for
|
|
180
|
+
* logs (`EgressRefusedError.message`), log that instead.
|
|
181
|
+
*
|
|
182
|
+
* @param required.err - The caught value.
|
|
183
|
+
* @returns e.g. `SqliteError(SQLITE_BUSY)`, or `TypeError cause=Error(ECONNREFUSED)`.
|
|
184
|
+
* @complexity O(1).
|
|
185
|
+
*/
|
|
186
|
+
export declare function describeErrorForLog(required: {
|
|
187
|
+
err: unknown;
|
|
188
|
+
}, _optional?: Record<string, never>): string;
|
|
37
189
|
//# sourceMappingURL=model-facing-tool-errors.d.ts.map
|