@novoagents/react 0.3.0-alpha.5 → 0.3.0-alpha.51
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 +198 -104
- package/LICENSE +19 -0
- package/README.md +8 -22
- package/dist/useNovoRun.js +36 -20
- package/dist/useNovoRunSelector.js +58 -38
- package/package.json +18 -19
package/CHANGELOG.md
CHANGED
|
@@ -2,108 +2,202 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
-
## 0.3.0-alpha.
|
|
5
|
+
## 0.3.0-alpha.50 - 2026-09-20
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
## 0.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
## 0.
|
|
102
|
-
|
|
103
|
-
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
7
|
+
- Retain nonblocking questions across run terminals and later runs. Questions
|
|
8
|
+
can be answered while the agent works; approval gates still pause execution.
|
|
9
|
+
|
|
10
|
+
## 0.3.0-alpha.49 - 2026-09-16
|
|
11
|
+
|
|
12
|
+
Companion release for `novoagents` 0.5.0-alpha.45. The exact peer carries
|
|
13
|
+
named skill resources, the generated context contract, and optional screenshot
|
|
14
|
+
export status. React runtime and stream revision 21 are unchanged.
|
|
15
|
+
|
|
16
|
+
## 0.3.0-alpha.48 - 2026-09-16
|
|
17
|
+
|
|
18
|
+
Companion release for `novoagents` 0.5.0-alpha.44. The exact peer includes
|
|
19
|
+
thread-scoped browser inventory, stable live-view identity, and screenshot
|
|
20
|
+
evidence status. The React runtime and stream revision 21 are unchanged.
|
|
21
|
+
|
|
22
|
+
## 0.3.0-alpha.47 - 2026-09-15
|
|
23
|
+
|
|
24
|
+
Companion release for `novoagents` 0.5.0-alpha.43 and stream revision 21. The
|
|
25
|
+
exact peer preserves optional text purpose in browser state while keeping run
|
|
26
|
+
completion authoritative. The React hook runtime is unchanged.
|
|
27
|
+
|
|
28
|
+
## 0.3.0-alpha.46 - 2026-09-15
|
|
29
|
+
|
|
30
|
+
Companion release for `novoagents` 0.5.0-alpha.42. The exact peer includes
|
|
31
|
+
thread admission epochs, explicit Stop/resume controls and attempt-only
|
|
32
|
+
maintenance cancellation. The React runtime is unchanged.
|
|
33
|
+
|
|
34
|
+
## 0.3.0-alpha.45 - 2026-09-15
|
|
35
|
+
|
|
36
|
+
Companion release for `novoagents` 0.5.0-alpha.41 and stream revision 20. The
|
|
37
|
+
exact peer includes worker cancellation state and updated orchestration
|
|
38
|
+
contracts. The React runtime is unchanged.
|
|
39
|
+
|
|
40
|
+
## 0.3.0-alpha.44 - 2026-09-14
|
|
41
|
+
|
|
42
|
+
Companion release for `novoagents` 0.5.0-alpha.40 and the worker/advisor tool
|
|
43
|
+
catalog. The React runtime is unchanged.
|
|
44
|
+
|
|
45
|
+
## 0.3.0-alpha.43 - 2026-09-09
|
|
46
|
+
|
|
47
|
+
Companion release for `novoagents` 0.5.0-alpha.39 and stream revision 19. The React runtime is unchanged.
|
|
48
|
+
|
|
49
|
+
## 0.3.0-alpha.42 - 2026-09-09
|
|
50
|
+
|
|
51
|
+
Companion release for `novoagents` 0.5.0-alpha.38. The exact peer carries context
|
|
52
|
+
audiences, thread credential attachments, execution authorization types, the
|
|
53
|
+
current error catalog, and updated managed-media documentation. The React runtime
|
|
54
|
+
is unchanged. Packed-consumer tests cover the SDK types and HTTP attachment
|
|
55
|
+
contract alongside React 18 compatibility.
|
|
56
|
+
|
|
57
|
+
## 0.3.0-alpha.41 - 2026-09-07
|
|
58
|
+
|
|
59
|
+
Companion release for `novoagents` 0.5.0-alpha.37. The exact peer carries the
|
|
60
|
+
managed operation blocking contract and blockable-tool catalog. The React
|
|
61
|
+
runtime is unchanged.
|
|
62
|
+
|
|
63
|
+
## 0.3.0-alpha.40 - 2026-09-06
|
|
64
|
+
|
|
65
|
+
Companion release for `novoagents` 0.5.0-alpha.36. The React runtime is
|
|
66
|
+
unchanged; the exact peer carries the corrected public type and error exports.
|
|
67
|
+
Packed-consumer verification now runs from a staged copy and exercises the
|
|
68
|
+
workspace peer resolver independently.
|
|
69
|
+
|
|
70
|
+
## 0.3.0-alpha.39 - 2026-09-06
|
|
71
|
+
|
|
72
|
+
Companion release for `novoagents` 0.5.0-alpha.35. The React runtime is
|
|
73
|
+
unchanged; the exact peer carries the combined private-input and managed-web
|
|
74
|
+
usage contracts.
|
|
75
|
+
|
|
76
|
+
## 0.3.0-alpha.38 - 2026-09-06
|
|
77
|
+
|
|
78
|
+
Companion release for `novoagents` 0.5.0-alpha.34. The React runtime is
|
|
79
|
+
unchanged; the exact peer carries protected browser input and context revision
|
|
80
|
+
contracts.
|
|
81
|
+
|
|
82
|
+
## 0.3.0-alpha.37 - 2026-09-03
|
|
83
|
+
|
|
84
|
+
Companion release for `novoagents` 0.5.0-alpha.33. The React runtime is
|
|
85
|
+
unchanged; the exact peer carries the new agent configuration warnings type.
|
|
86
|
+
|
|
87
|
+
## 0.3.0-alpha.36 - 2026-09-01
|
|
88
|
+
|
|
89
|
+
Companion release for `novoagents` 0.5.0-alpha.32.
|
|
90
|
+
|
|
91
|
+
- Controller ownership now rides the external-store snapshot (run key and
|
|
92
|
+
controller travel with the subscribed state), so an abandoned concurrent
|
|
93
|
+
render can no longer strand ownership in a render-written ref. Selector
|
|
94
|
+
readers isolate speculative renders while preserving equal object identity
|
|
95
|
+
across inline selector changes: the committed selection lives in a
|
|
96
|
+
`useState`-held cell that is still read during render, by design, mirroring
|
|
97
|
+
React's own `useSyncExternalStoreWithSelector` shim. Behavior is unchanged
|
|
98
|
+
across React 18 and 19, and the package now passes the canonical hooks lint
|
|
99
|
+
rules.
|
|
100
|
+
|
|
101
|
+
## 0.3.0-alpha.35 - 2026-08-26
|
|
102
|
+
|
|
103
|
+
- Companion release for `novoagents` 0.5.0-alpha.31. GitHub connection and
|
|
104
|
+
agent repository grants now carry exact immutable repository identities
|
|
105
|
+
end to end; an explicit empty agent selection grants no repositories.
|
|
106
|
+
|
|
107
|
+
## 0.3.0-alpha.34 - 2026-08-26
|
|
108
|
+
|
|
109
|
+
- Companion release for `novoagents` 0.5.0-alpha.30. The React runtime is
|
|
110
|
+
unchanged; the exact peer carries the immutable repository id on grantable
|
|
111
|
+
GitHub repositories.
|
|
112
|
+
|
|
113
|
+
## 0.3.0-alpha.33 - 2026-08-26
|
|
114
|
+
|
|
115
|
+
- Companion release for `novoagents` 0.5.0-alpha.29. The React runtime is
|
|
116
|
+
unchanged; the exact peer carries the discriminated usage-lineage and
|
|
117
|
+
debit-group identity contract.
|
|
118
|
+
|
|
119
|
+
## 0.3.0-alpha.32 - 2026-08-25
|
|
120
|
+
|
|
121
|
+
- Companion release for `novoagents` 0.5.0-alpha.28. It carries the exact peer
|
|
122
|
+
for scheduled wake-ups, stream protocol 18, and customer wake-up
|
|
123
|
+
cancellation. The React runtime is unchanged.
|
|
124
|
+
|
|
125
|
+
## 0.3.0-alpha.31 - 2026-08-21
|
|
126
|
+
|
|
127
|
+
- Companion release for `novoagents` 0.5.0-alpha.27. The controller can follow
|
|
128
|
+
ordered thread segments with an injected cursor codec; React state exposes
|
|
129
|
+
the segment ledger while preserving the transcript across engine-owned runs.
|
|
130
|
+
|
|
131
|
+
## 0.3.0-alpha.27 - 2026-08-20
|
|
132
|
+
|
|
133
|
+
- Companion release for `novoagents` 0.5.0-alpha.25. The React runtime is
|
|
134
|
+
unchanged; the exact peer carries autonomous run starts and all-attempt Stop
|
|
135
|
+
semantics for background subagents.
|
|
136
|
+
|
|
137
|
+
## 0.3.0-alpha.26 - 2026-08-20
|
|
138
|
+
|
|
139
|
+
- Companion release for `novoagents` 0.5.0-alpha.24. It carries the exact peer
|
|
140
|
+
for wire protocol 16, the shared GitHub installation contract, and the
|
|
141
|
+
`ask_question` lifecycle whose first public terminal is the user's real
|
|
142
|
+
resolution.
|
|
143
|
+
|
|
144
|
+
## 0.3.0-alpha.25 - 2026-08-20
|
|
145
|
+
|
|
146
|
+
- Companion release for `novoagents` 0.5.0-alpha.23. It carries the exact peer
|
|
147
|
+
required for stream schema 14, including background worker task metadata and
|
|
148
|
+
`data-novo-worker-report` message parts.
|
|
149
|
+
|
|
150
|
+
## 0.3.0-alpha.24 - 2026-08-18
|
|
151
|
+
|
|
152
|
+
- Companion release for `novoagents` 0.5.0-alpha.22. The hook runtime is
|
|
153
|
+
unchanged; the package carries an exact peer on the SDK version whose browser
|
|
154
|
+
run controller it binds. Consumers rendering `run.messages` now see one
|
|
155
|
+
`data-novo-heartbeat` part per tool wait (see the core changelog).
|
|
156
|
+
|
|
157
|
+
## 0.3.0-alpha.23 - 2026-08-17
|
|
158
|
+
|
|
159
|
+
- Companion release for `novoagents` 0.5.0-alpha.21. The hook runtime is
|
|
160
|
+
unchanged; the package carries an exact peer on the SDK version whose browser
|
|
161
|
+
run controller it binds.
|
|
162
|
+
|
|
163
|
+
## 0.3.0-alpha.22 - 2026-08-17
|
|
164
|
+
|
|
165
|
+
- Companion release for `novoagents` 0.5.0-alpha.20. The hook runtime is
|
|
166
|
+
unchanged; the package carries an exact peer on the SDK version whose browser
|
|
167
|
+
run controller it binds.
|
|
168
|
+
|
|
169
|
+
## 0.3.0-alpha.21 - 2026-08-17
|
|
170
|
+
|
|
171
|
+
- Companion release for `novoagents` 0.5.0-alpha.19. The hook runtime is
|
|
172
|
+
unchanged; the package carries an exact peer on the SDK version whose browser
|
|
173
|
+
run controller it binds.
|
|
174
|
+
|
|
175
|
+
## 0.3.0-alpha.20 - 2026-08-16
|
|
176
|
+
|
|
177
|
+
- Companion release for `novoagents` 0.5.0-alpha.18. The hook runtime is
|
|
178
|
+
unchanged; the package carries an exact peer on the SDK version whose browser
|
|
179
|
+
run controller it binds.
|
|
180
|
+
- `0.3.0-alpha.19` was published through npm instead of pnpm and therefore
|
|
181
|
+
preserved the workspace protocol in its peer metadata. It was never tagged
|
|
182
|
+
`latest`; use this build instead.
|
|
183
|
+
|
|
184
|
+
## 0.3.0-alpha.18 - 2026-08-12
|
|
185
|
+
|
|
186
|
+
- Companion release for `novoagents` 0.5.0-alpha.17. The hook runtime is
|
|
187
|
+
unchanged; the package carries an exact peer on the SDK version whose browser
|
|
188
|
+
run controller it binds.
|
|
189
|
+
|
|
190
|
+
## 0.3.0-alpha.17 - 2026-08-12
|
|
191
|
+
|
|
192
|
+
- Companion release for `novoagents` 0.5.0-alpha.16. The hook runtime is
|
|
193
|
+
unchanged; the package carries an exact peer on the SDK version whose browser
|
|
194
|
+
run controller it binds.
|
|
195
|
+
|
|
196
|
+
## 0.3.0-alpha.16 - 2026-08-12
|
|
197
|
+
|
|
198
|
+
- Companion release for `novoagents` 0.5.0-alpha.15. The hook runtime is
|
|
199
|
+
unchanged; the package carries an exact peer on the SDK version whose browser
|
|
200
|
+
run controller it binds.
|
|
201
|
+
|
|
202
|
+
Earlier prerelease builds are unsupported. Their immutable package artifacts
|
|
203
|
+
and Git history retain their release records.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2026 Robert Pinckney. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software and associated documentation files (the "Software") are
|
|
4
|
+
proprietary and confidential. No license, express or implied, is granted
|
|
5
|
+
to any party to use, copy, modify, merge, publish, distribute, sublicense,
|
|
6
|
+
or sell copies of the Software without the prior written permission of
|
|
7
|
+
the copyright holder.
|
|
8
|
+
|
|
9
|
+
Unauthorized use, reproduction, or distribution of the Software, or any
|
|
10
|
+
portion of it, is strictly prohibited and may result in civil and criminal
|
|
11
|
+
penalties.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
16
|
+
THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
17
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
|
18
|
+
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @novoagents/react
|
|
2
2
|
|
|
3
|
+
> Developer access is currently closed. Novo Agents powers the agents in Novo North. This package is maintained for approved integrations; installing it does not grant service access.
|
|
4
|
+
|
|
3
5
|
Headless React bindings for the canonical browser run controller in
|
|
4
6
|
`novoagents/browser`. The package renders nothing, ships no CSS, and never
|
|
5
7
|
accepts a Novo API key. Browser requests go only to your authenticated proxy
|
|
@@ -49,25 +51,9 @@ configure `interactionDiscovery: { list, intervalMs? }` on the controller.
|
|
|
49
51
|
Discovery is generation-fenced, runs while the SSE connection is idle, and
|
|
50
52
|
reconciles DB-only requests into the same canonical pending queue.
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
`useNovoRun({ createController, runKey })`.
|
|
59
|
-
- `useNovoInteractions({ endpoint, run })` → configure the controller's
|
|
60
|
-
interaction transport, then call `useNovoInteractions(run.controller)`.
|
|
61
|
-
- `resolveNovoInteractions` → `createHttpInteractionResolveTransport`.
|
|
62
|
-
|
|
63
|
-
There are no compatibility aliases for the alpha API.
|
|
64
|
-
|
|
65
|
-
## Publishing
|
|
66
|
-
|
|
67
|
-
After publishing `novoagents`, publish with
|
|
68
|
-
`pnpm publish --access public --tag alpha`: pnpm rewrites the `workspace:*`
|
|
69
|
-
peer dependency on `novoagents` to the exact packed version. Publish the two
|
|
70
|
-
alpha packages in lockstep so both `alpha` tags always form one compatible
|
|
71
|
-
browser-controller installation. Do not publish the workspace source with
|
|
72
|
-
`npm publish`; npm does not perform pnpm's workspace-protocol rewrite. Verify
|
|
73
|
-
the registry peer metadata before moving `latest`.
|
|
54
|
+
Questions with `blocking: false` stay in the pending queue across run terminals
|
|
55
|
+
and new runs. An explicit Stop cancels them. They do not change the run status or require a paused controller
|
|
56
|
+
to resolve. Approval gates remain blocking. Use a thread-follow source and
|
|
57
|
+
controller for this ongoing conversation, and scope discovery to the thread so
|
|
58
|
+
questions from older runs remain available. The example above follows one finite
|
|
59
|
+
run only.
|
package/dist/useNovoRun.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';
|
|
2
|
+
import { useEffect, useMemo, useRef, useState, useSyncExternalStore, } from 'react';
|
|
3
3
|
export const NOVO_IDLE_RUN_STATE = Object.freeze({
|
|
4
4
|
messages: Object.freeze([]),
|
|
5
5
|
status: 'queued',
|
|
6
6
|
progress: null,
|
|
7
7
|
pendingInteractions: Object.freeze([]),
|
|
8
8
|
tasks: Object.freeze([]),
|
|
9
|
+
segments: Object.freeze([]),
|
|
9
10
|
steps: Object.freeze([]),
|
|
10
11
|
browserSessions: Object.freeze([]),
|
|
11
12
|
warnings: Object.freeze([]),
|
|
@@ -21,7 +22,17 @@ function createControllerSlot() {
|
|
|
21
22
|
const listeners = new Set();
|
|
22
23
|
let controller;
|
|
23
24
|
let unsubscribe;
|
|
25
|
+
let snapshot = {
|
|
26
|
+
controller: undefined,
|
|
27
|
+
runKey: UNOWNED_RUN_KEY,
|
|
28
|
+
state: NOVO_IDLE_RUN_STATE,
|
|
29
|
+
};
|
|
24
30
|
const publish = () => {
|
|
31
|
+
snapshot = {
|
|
32
|
+
...snapshot,
|
|
33
|
+
state: controller?.getSnapshot() ??
|
|
34
|
+
NOVO_IDLE_RUN_STATE,
|
|
35
|
+
};
|
|
25
36
|
for (const listener of listeners)
|
|
26
37
|
listener();
|
|
27
38
|
};
|
|
@@ -30,15 +41,20 @@ function createControllerSlot() {
|
|
|
30
41
|
listeners.add(listener);
|
|
31
42
|
return () => listeners.delete(listener);
|
|
32
43
|
},
|
|
33
|
-
getSnapshot: () =>
|
|
34
|
-
NOVO_IDLE_RUN_STATE,
|
|
44
|
+
getSnapshot: () => snapshot,
|
|
35
45
|
getController: () => controller,
|
|
36
|
-
setController(next) {
|
|
37
|
-
if (controller === next)
|
|
46
|
+
setController(next, runKey = UNOWNED_RUN_KEY) {
|
|
47
|
+
if (controller === next && snapshot.runKey === runKey)
|
|
38
48
|
return;
|
|
39
49
|
unsubscribe?.();
|
|
40
50
|
controller = next;
|
|
41
51
|
unsubscribe = next?.subscribe(publish);
|
|
52
|
+
snapshot = {
|
|
53
|
+
controller: next,
|
|
54
|
+
runKey,
|
|
55
|
+
state: next?.getSnapshot() ??
|
|
56
|
+
NOVO_IDLE_RUN_STATE,
|
|
57
|
+
};
|
|
42
58
|
publish();
|
|
43
59
|
},
|
|
44
60
|
};
|
|
@@ -50,11 +66,10 @@ function createControllerSlot() {
|
|
|
50
66
|
*/
|
|
51
67
|
export function useNovoRun(options) {
|
|
52
68
|
const factoryRef = useRef(options.createController);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const slot =
|
|
57
|
-
const ownedRunKeyRef = useRef(UNOWNED_RUN_KEY);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
factoryRef.current = options.createController;
|
|
71
|
+
}, [options.createController]);
|
|
72
|
+
const [slot] = useState(() => createControllerSlot());
|
|
58
73
|
// Normalize before any dependency array: `enabled: undefined <-> true` and
|
|
59
74
|
// `runKey: undefined <-> null` toggles are identity changes and must not
|
|
60
75
|
// dispose and recreate a live controller.
|
|
@@ -66,14 +81,10 @@ export function useNovoRun(options) {
|
|
|
66
81
|
return;
|
|
67
82
|
}
|
|
68
83
|
const controller = factoryRef.current();
|
|
69
|
-
|
|
70
|
-
slot.setController(controller);
|
|
84
|
+
slot.setController(controller, runKey);
|
|
71
85
|
return () => {
|
|
72
86
|
controller.stop();
|
|
73
87
|
controller.dispose();
|
|
74
|
-
if (ownedRunKeyRef.current === runKey) {
|
|
75
|
-
ownedRunKeyRef.current = UNOWNED_RUN_KEY;
|
|
76
|
-
}
|
|
77
88
|
if (slot.getController() === controller)
|
|
78
89
|
slot.setController(undefined);
|
|
79
90
|
};
|
|
@@ -81,16 +92,16 @@ export function useNovoRun(options) {
|
|
|
81
92
|
useEffect(() => {
|
|
82
93
|
if (enabled &&
|
|
83
94
|
options.autoStart !== false &&
|
|
84
|
-
|
|
95
|
+
slot.getSnapshot().runKey === runKey) {
|
|
85
96
|
void slot.getController()?.start();
|
|
86
97
|
}
|
|
87
98
|
}, [options.autoStart, enabled, runKey, slot]);
|
|
88
|
-
const
|
|
89
|
-
const ownsRenderedKey = enabled &&
|
|
99
|
+
const observed = useSyncExternalStore(slot.subscribe, slot.getSnapshot, () => IDLE_SLOT_SNAPSHOT);
|
|
100
|
+
const ownsRenderedKey = enabled && observed.runKey === runKey;
|
|
90
101
|
const state = ownsRenderedKey
|
|
91
|
-
?
|
|
102
|
+
? observed.state
|
|
92
103
|
: NOVO_IDLE_RUN_STATE;
|
|
93
|
-
const controller = ownsRenderedKey ?
|
|
104
|
+
const controller = ownsRenderedKey ? observed.controller : undefined;
|
|
94
105
|
return useMemo(() => ({
|
|
95
106
|
...state,
|
|
96
107
|
controller,
|
|
@@ -104,3 +115,8 @@ export function useNovoRun(options) {
|
|
|
104
115
|
}), [controller, state]);
|
|
105
116
|
}
|
|
106
117
|
const UNOWNED_RUN_KEY = Symbol('unowned Novo run key');
|
|
118
|
+
const IDLE_SLOT_SNAPSHOT = Object.freeze({
|
|
119
|
+
controller: undefined,
|
|
120
|
+
runKey: UNOWNED_RUN_KEY,
|
|
121
|
+
state: NOVO_IDLE_RUN_STATE,
|
|
122
|
+
});
|
|
@@ -1,47 +1,67 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useCallback, useEffect, useMemo,
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState, useSyncExternalStore, } from 'react';
|
|
3
3
|
import { NOVO_IDLE_RUN_STATE } from './useNovoRun.js';
|
|
4
4
|
/** Concurrent-safe selector binding with selector/equality identity support. */
|
|
5
5
|
export function useNovoRunSelector(controller, selector, isEqual = Object.is) {
|
|
6
|
-
const committedSelection =
|
|
7
|
-
|
|
8
|
-
});
|
|
9
|
-
const idleState = NOVO_IDLE_RUN_STATE;
|
|
10
|
-
const [getSnapshot, getServerSnapshot] = useMemo(() => {
|
|
11
|
-
let hasMemo = false;
|
|
12
|
-
let memoizedState;
|
|
13
|
-
let memoizedSelection;
|
|
14
|
-
const select = (state) => {
|
|
15
|
-
if (!hasMemo) {
|
|
16
|
-
hasMemo = true;
|
|
17
|
-
memoizedState = state;
|
|
18
|
-
const nextSelection = selector(state);
|
|
19
|
-
const committed = committedSelection.current;
|
|
20
|
-
memoizedSelection =
|
|
21
|
-
committed.hasValue && isEqual(committed.value, nextSelection)
|
|
22
|
-
? committed.value
|
|
23
|
-
: nextSelection;
|
|
24
|
-
return memoizedSelection;
|
|
25
|
-
}
|
|
26
|
-
if (Object.is(memoizedState, state))
|
|
27
|
-
return memoizedSelection;
|
|
28
|
-
const nextSelection = selector(state);
|
|
29
|
-
memoizedState = state;
|
|
30
|
-
if (isEqual(memoizedSelection, nextSelection))
|
|
31
|
-
return memoizedSelection;
|
|
32
|
-
memoizedSelection = nextSelection;
|
|
33
|
-
return memoizedSelection;
|
|
34
|
-
};
|
|
35
|
-
const serverSelection = select(idleState);
|
|
36
|
-
return [
|
|
37
|
-
() => select(controller?.getSnapshot() ?? idleState),
|
|
38
|
-
() => serverSelection,
|
|
39
|
-
];
|
|
40
|
-
}, [controller, isEqual, selector]);
|
|
6
|
+
const [committedSelection] = useState(() => createCommittedSelection());
|
|
7
|
+
const [getSnapshot, getServerSnapshot] = useMemo(() => createSelectionReader(controller, selector, isEqual, NOVO_IDLE_RUN_STATE, committedSelection), [committedSelection, controller, isEqual, selector]);
|
|
41
8
|
const subscribe = useCallback((listener) => controller?.subscribe(listener) ?? (() => { }), [controller]);
|
|
42
9
|
const selection = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
43
10
|
useEffect(() => {
|
|
44
|
-
committedSelection.
|
|
45
|
-
}, [selection]);
|
|
11
|
+
committedSelection.write(selection);
|
|
12
|
+
}, [committedSelection, selection]);
|
|
46
13
|
return selection;
|
|
47
14
|
}
|
|
15
|
+
// Deliberately a mutable cell read during render (via getSnapshot) and
|
|
16
|
+
// written from the commit effect, mirroring React's own
|
|
17
|
+
// useSyncExternalStoreWithSelector shim. The hooks lint layer cannot see a
|
|
18
|
+
// closure-held cell; do not "fix" this back into a ref, which it would flag.
|
|
19
|
+
function createCommittedSelection() {
|
|
20
|
+
let committed = {
|
|
21
|
+
hasValue: false,
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
read: () => committed,
|
|
25
|
+
write: (value) => {
|
|
26
|
+
committed = { hasValue: true, value };
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function createSelectionReader(controller, selector, isEqual, idleState, committedSelection) {
|
|
31
|
+
let hasValue = false;
|
|
32
|
+
let value;
|
|
33
|
+
let memoizedState;
|
|
34
|
+
let hasServerValue = false;
|
|
35
|
+
let serverValue;
|
|
36
|
+
const select = (state) => {
|
|
37
|
+
if (Object.is(memoizedState, state) && hasValue)
|
|
38
|
+
return value;
|
|
39
|
+
const next = selector(state);
|
|
40
|
+
memoizedState = state;
|
|
41
|
+
if (!hasValue) {
|
|
42
|
+
hasValue = true;
|
|
43
|
+
const committed = committedSelection.read();
|
|
44
|
+
value =
|
|
45
|
+
committed.hasValue && isEqual(committed.value, next)
|
|
46
|
+
? committed.value
|
|
47
|
+
: next;
|
|
48
|
+
}
|
|
49
|
+
else if (!isEqual(value, next)) {
|
|
50
|
+
value = next;
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
};
|
|
54
|
+
const selectServer = () => {
|
|
55
|
+
if (hasServerValue)
|
|
56
|
+
return serverValue;
|
|
57
|
+
const next = selector(idleState);
|
|
58
|
+
const committed = committedSelection.read();
|
|
59
|
+
serverValue =
|
|
60
|
+
committed.hasValue && isEqual(committed.value, next)
|
|
61
|
+
? committed.value
|
|
62
|
+
: next;
|
|
63
|
+
hasServerValue = true;
|
|
64
|
+
return serverValue;
|
|
65
|
+
};
|
|
66
|
+
return [() => select(controller?.getSnapshot() ?? idleState), selectServer];
|
|
67
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@novoagents/react",
|
|
3
|
-
"version": "0.3.0-alpha.
|
|
3
|
+
"version": "0.3.0-alpha.51",
|
|
4
4
|
"description": "Browser-safe React hooks for Novo Agents streams and interactions.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -22,27 +22,26 @@
|
|
|
22
22
|
"publishConfig": {
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"novoagents": "0.5.0-alpha.47",
|
|
27
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/react": "19.2.18",
|
|
31
|
+
"@types/react-dom": "19.2.7",
|
|
32
|
+
"happy-dom": "20.14.0",
|
|
33
|
+
"novoagents": "0.5.0-alpha.47",
|
|
34
|
+
"react": "19.2.8",
|
|
35
|
+
"react-dom": "19.2.8",
|
|
36
|
+
"typescript": "6.0.3",
|
|
37
|
+
"vite": "8.2.2",
|
|
38
|
+
"vitest": "5.0.0"
|
|
39
|
+
},
|
|
25
40
|
"scripts": {
|
|
26
41
|
"build": "tsc -p tsconfig.json && node scripts/check-dist.mjs",
|
|
27
42
|
"clean": "rm -rf dist",
|
|
28
43
|
"test": "vitest run && pnpm run test:react18",
|
|
29
44
|
"test:react18": "node scripts/test-react18.mjs",
|
|
30
|
-
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
31
|
-
"prepack": "pnpm run clean && pnpm run build"
|
|
32
|
-
},
|
|
33
|
-
"peerDependencies": {
|
|
34
|
-
"novoagents": "workspace:*",
|
|
35
|
-
"react": "^18.0.0 || ^19.0.0"
|
|
36
|
-
},
|
|
37
|
-
"devDependencies": {
|
|
38
|
-
"@types/react": "19.2.17",
|
|
39
|
-
"@types/react-dom": "19.2.3",
|
|
40
|
-
"happy-dom": "20.11.1",
|
|
41
|
-
"novoagents": "workspace:*",
|
|
42
|
-
"react": "19.2.7",
|
|
43
|
-
"react-dom": "19.2.7",
|
|
44
|
-
"typescript": "catalog:",
|
|
45
|
-
"vite": "catalog:",
|
|
46
|
-
"vitest": "catalog:"
|
|
45
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
47
46
|
}
|
|
48
|
-
}
|
|
47
|
+
}
|