@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.
Files changed (131) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +19 -1
  3. package/dist/agent-tools.d.ts +1 -0
  4. package/dist/approval.d.ts +134 -0
  5. package/dist/approval.js +141 -0
  6. package/dist/fetch/cache.d.ts +129 -0
  7. package/dist/fetch/cache.js +243 -0
  8. package/dist/fetch/index.d.ts +5 -0
  9. package/dist/fetch/index.js +3 -0
  10. package/dist/fetch/types.d.ts +93 -0
  11. package/dist/fetch/types.js +2 -0
  12. package/dist/index.d.ts +6 -1
  13. package/dist/index.js +6 -1
  14. package/dist/model-facing-tool-errors.d.ts +152 -0
  15. package/dist/model-facing-tool-errors.js +124 -0
  16. package/dist/native-regexp.d.ts +10 -0
  17. package/dist/native-regexp.js +2 -0
  18. package/dist/primitives/index.d.ts +2 -0
  19. package/dist/primitives/index.js +1 -0
  20. package/dist/primitives/sse.d.ts +60 -0
  21. package/dist/primitives/sse.js +122 -0
  22. package/dist/redact.js +4 -3
  23. package/dist/registration-kit.d.ts +13 -0
  24. package/dist/registration-kit.js +12 -0
  25. package/dist/settings/agent-tools.d.ts +10 -0
  26. package/dist/settings/agent-tools.js +204 -0
  27. package/dist/settings/agent-value-write-tools.d.ts +22 -0
  28. package/dist/settings/agent-value-write-tools.js +157 -0
  29. package/dist/settings/agent-writable-preferences.d.ts +67 -0
  30. package/dist/settings/agent-writable-preferences.js +132 -0
  31. package/dist/settings/agent-write-denylist.d.ts +19 -0
  32. package/dist/settings/agent-write-denylist.js +15 -0
  33. package/dist/settings/change-feed.d.ts +83 -0
  34. package/dist/settings/change-feed.js +61 -0
  35. package/dist/settings/definitions-dispatch.d.ts +56 -0
  36. package/dist/settings/definitions-dispatch.js +86 -0
  37. package/dist/settings/dictionaries/index.d.ts +48 -0
  38. package/dist/settings/dictionaries/index.js +69 -0
  39. package/dist/settings/dictionaries/settings-dialog.ar.d.ts +3 -0
  40. package/dist/settings/dictionaries/settings-dialog.ar.js +27 -0
  41. package/dist/settings/dictionaries/settings-dialog.bn.d.ts +3 -0
  42. package/dist/settings/dictionaries/settings-dialog.bn.js +27 -0
  43. package/dist/settings/dictionaries/settings-dialog.de.d.ts +3 -0
  44. package/dist/settings/dictionaries/settings-dialog.de.js +27 -0
  45. package/dist/settings/dictionaries/settings-dialog.es.d.ts +3 -0
  46. package/dist/settings/dictionaries/settings-dialog.es.js +27 -0
  47. package/dist/settings/dictionaries/settings-dialog.fa.d.ts +3 -0
  48. package/dist/settings/dictionaries/settings-dialog.fa.js +27 -0
  49. package/dist/settings/dictionaries/settings-dialog.fr.d.ts +3 -0
  50. package/dist/settings/dictionaries/settings-dialog.fr.js +27 -0
  51. package/dist/settings/dictionaries/settings-dialog.hi.d.ts +3 -0
  52. package/dist/settings/dictionaries/settings-dialog.hi.js +27 -0
  53. package/dist/settings/dictionaries/settings-dialog.hu.d.ts +3 -0
  54. package/dist/settings/dictionaries/settings-dialog.hu.js +27 -0
  55. package/dist/settings/dictionaries/settings-dialog.id.d.ts +3 -0
  56. package/dist/settings/dictionaries/settings-dialog.id.js +27 -0
  57. package/dist/settings/dictionaries/settings-dialog.it.d.ts +3 -0
  58. package/dist/settings/dictionaries/settings-dialog.it.js +27 -0
  59. package/dist/settings/dictionaries/settings-dialog.ja.d.ts +3 -0
  60. package/dist/settings/dictionaries/settings-dialog.ja.js +27 -0
  61. package/dist/settings/dictionaries/settings-dialog.ko.d.ts +3 -0
  62. package/dist/settings/dictionaries/settings-dialog.ko.js +27 -0
  63. package/dist/settings/dictionaries/settings-dialog.pl.d.ts +3 -0
  64. package/dist/settings/dictionaries/settings-dialog.pl.js +27 -0
  65. package/dist/settings/dictionaries/settings-dialog.pt-BR.d.ts +3 -0
  66. package/dist/settings/dictionaries/settings-dialog.pt-BR.js +27 -0
  67. package/dist/settings/dictionaries/settings-dialog.ru.d.ts +3 -0
  68. package/dist/settings/dictionaries/settings-dialog.ru.js +27 -0
  69. package/dist/settings/dictionaries/settings-dialog.th.d.ts +3 -0
  70. package/dist/settings/dictionaries/settings-dialog.th.js +27 -0
  71. package/dist/settings/dictionaries/settings-dialog.tr.d.ts +3 -0
  72. package/dist/settings/dictionaries/settings-dialog.tr.js +27 -0
  73. package/dist/settings/dictionaries/settings-dialog.uk.d.ts +3 -0
  74. package/dist/settings/dictionaries/settings-dialog.uk.js +27 -0
  75. package/dist/settings/dictionaries/settings-dialog.ur.d.ts +3 -0
  76. package/dist/settings/dictionaries/settings-dialog.ur.js +27 -0
  77. package/dist/settings/dictionaries/settings-dialog.zh-CN.d.ts +3 -0
  78. package/dist/settings/dictionaries/settings-dialog.zh-CN.js +27 -0
  79. package/dist/settings/dictionaries/settings-dialog.zh-TW.d.ts +3 -0
  80. package/dist/settings/dictionaries/settings-dialog.zh-TW.js +27 -0
  81. package/dist/settings/ensure-definitions.d.ts +101 -0
  82. package/dist/settings/ensure-definitions.js +101 -0
  83. package/dist/settings/errors.d.ts +42 -0
  84. package/dist/settings/errors.js +46 -0
  85. package/dist/settings/express/cms-adapter.d.ts +20 -0
  86. package/dist/settings/express/cms-adapter.js +124 -0
  87. package/dist/settings/express/contracts.d.ts +135 -0
  88. package/dist/settings/express/contracts.js +2 -0
  89. package/dist/settings/express/http.d.ts +10 -0
  90. package/dist/settings/express/http.js +211 -0
  91. package/dist/settings/express/index.d.ts +5 -0
  92. package/dist/settings/express/index.js +4 -0
  93. package/dist/settings/express/shared.d.ts +68 -0
  94. package/dist/settings/express/shared.js +54 -0
  95. package/dist/settings/express/stream.d.ts +24 -0
  96. package/dist/settings/express/stream.js +147 -0
  97. package/dist/settings/index.d.ts +40 -0
  98. package/dist/settings/index.js +37 -0
  99. package/dist/settings/ports.d.ts +118 -0
  100. package/dist/settings/ports.js +2 -0
  101. package/dist/settings/principal-lookup.d.ts +9 -0
  102. package/dist/settings/principal-lookup.js +2 -0
  103. package/dist/settings/purge-service.d.ts +54 -0
  104. package/dist/settings/purge-service.js +106 -0
  105. package/dist/settings/repo.memory.d.ts +119 -0
  106. package/dist/settings/repo.memory.js +199 -0
  107. package/dist/settings/settings.d.ts +139 -0
  108. package/dist/settings/settings.js +259 -0
  109. package/dist/settings/tool-registrations.d.ts +54 -0
  110. package/dist/settings/tool-registrations.js +250 -0
  111. package/dist/settings/types.d.ts +125 -0
  112. package/dist/settings/types.js +3 -0
  113. package/dist/settings/ui-tab-definitions.d.ts +41 -0
  114. package/dist/settings/ui-tab-definitions.js +148 -0
  115. package/dist/settings/write-service.d.ts +320 -0
  116. package/dist/settings/write-service.js +683 -0
  117. package/dist/surface-exchanges.d.ts +209 -0
  118. package/dist/surface-exchanges.js +55 -0
  119. package/dist/text/escape.d.ts +20 -0
  120. package/dist/text/escape.js +23 -0
  121. package/dist/text/index.d.ts +1 -0
  122. package/dist/text/index.js +1 -0
  123. package/dist/tool-metadata.d.ts +71 -0
  124. package/dist/tool-metadata.js +56 -0
  125. package/dist/tool-permissions.d.ts +76 -0
  126. package/dist/tool-permissions.js +57 -0
  127. package/dist/tool-registry.d.ts +19 -1
  128. package/dist/tool-registry.js +44 -0
  129. package/package.json +79 -4
  130. package/plugin-directory.d.ts +32 -0
  131. 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,3 @@
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
+ //# sourceMappingURL=index.js.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
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.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 { createToolRegistry, isReadOnlyTool, ToolInputError } from './tool-registry.js';
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