nuspace 0.2.0__tar.gz → 0.2.1__tar.gz
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.
- {nuspace-0.2.0 → nuspace-0.2.1}/CHANGELOG.md +13 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/PKG-INFO +6 -6
- {nuspace-0.2.0 → nuspace-0.2.1}/README.md +1 -1
- {nuspace-0.2.0 → nuspace-0.2.1}/examples/apps.py +4 -4
- {nuspace-0.2.0 → nuspace-0.2.1}/examples/pages.py +12 -12
- {nuspace-0.2.0 → nuspace-0.2.1}/examples/web.py +2 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/pyproject.toml +7 -5
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/agent/model.py +5 -5
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/agent/ops.py +1 -1
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/agent/prompt.py +7 -6
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/agent/runner.py +7 -7
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/agent/shapes.py +12 -12
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/apps/runner.py +10 -8
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/apps/shapes.py +8 -8
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/chat/shapes.py +2 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/cli/main.py +1 -1
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/core/host.py +11 -11
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/core/session.py +8 -8
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/core/shapes.py +9 -8
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/core/tpl.py +9 -9
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/core/ui.py +5 -5
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/pages/runner.py +74 -47
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/pages/shapes.py +14 -14
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/apps/driver.py +2 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/apps/ref.py +2 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/chat/driver.py +3 -3
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/chat/ref.py +2 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/lens/driver.py +2 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/lens/ref.py +2 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/lens/reflect.py +2 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/nav.py +2 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/pages/driver.py +2 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/pages/ref.py +2 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/pages/session.py +9 -9
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/serve/__init__.py +2 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/serve/app.py +1 -1
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/serve/fabric.py +1 -1
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/serve/session.py +4 -4
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/serve/shell.py +3 -3
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/space.py +1 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/package-lock.json +2 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/package.json +1 -1
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/pyproject.toml +1 -1
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/app/router.ts +1 -1
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/app/theme.ts +1 -1
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/components/index.ts +3 -3
- nuspace-0.2.1/src/nuspace/web/ui/src/components/page-header.tsx +180 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/design/apps.ts +2 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/design/document.ts +201 -43
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/design/index.ts +1 -3
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/design/tokens.css +40 -5
- nuspace-0.2.1/src/nuspace/web/ui/src/refs/pages/Canvas.tsx +1349 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/pages/Code.tsx +38 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/pages/Program.tsx +15 -90
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/pages/ProseRef.tsx +63 -11
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/pages/Slash.tsx +47 -44
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/pages/blocks.ts +8 -6
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/pages/pages.tsx +16 -8
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/pages/state.ts +31 -7
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/wire.py +2 -2
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/apps/conftest.py +7 -7
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/apps/test_changed_key.py +3 -3
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/apps/test_ops.py +6 -6
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/pages/conftest.py +13 -13
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/web/test_apps_driver.py +3 -3
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/web/test_lens_driver.py +3 -3
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/web/test_lens_reflect.py +11 -11
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/web/test_pages_driver.py +3 -3
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/web/test_serve.py +5 -5
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/web/test_supervised.py +9 -9
- nuspace-0.2.0/src/nuspace/web/ui/src/components/page-header.tsx +0 -275
- nuspace-0.2.0/src/nuspace/web/ui/src/design/header.ts +0 -150
- nuspace-0.2.0/src/nuspace/web/ui/src/refs/pages/Canvas.tsx +0 -839
- {nuspace-0.2.0 → nuspace-0.2.1}/.github/workflows/publish-nuspace-ui.yml +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/.github/workflows/publish-nuspace.yml +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/.gitignore +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/.pre-commit-config.yaml +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/LICENSE.md +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/Makefile +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/__init__.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/_root.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/agent/__init__.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/apps/__init__.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/apps/ops.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/chat/__init__.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/chat/ops.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/cli/__init__.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/cli/__main__.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/cli/_meta.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/core/__init__.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/core/ids.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/pages/__init__.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/pages/ops.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/__init__.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/apps/__init__.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/arms.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/chat/__init__.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/lens/__init__.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/pages/__init__.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/.gitignore +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/biome.json +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/index.html +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/App.tsx +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/app/connect.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/app/local.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/app/surfaces.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/app/wire.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/components/rail/header.tsx +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/components/rail/index.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/components/rail/roving-focus.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/components/rail/row-input.tsx +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/components/rail/row.tsx +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/components/section-status-dot.tsx +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/design/chat.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/design/lens.css +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/design/lens.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/design/rail.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/design/section-status.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/design/shell.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/index.css +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/main.tsx +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/apps/Rail.tsx +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/apps/Source.tsx +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/apps/apps.tsx +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/apps/ops.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/apps/state.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/apps/types.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/chat/chat.tsx +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/chat/ops.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/chat/types.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/lens/lens.tsx +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/lens/types.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/nav/nav.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/pages/ProseRef.test.tsx +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/pages/Rail.tsx +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/pages/monaco-impl.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/pages/monaco.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/pages/ops.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/pages/prose.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/pages/types.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/refs/register.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/src/vite-env.d.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/tsconfig.app.json +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/tsconfig.json +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/tsconfig.node.json +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/vite.config.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/src/nuspace/web/ui/vitest.config.ts +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/apps/__init__.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/apps/test_lifecycle.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/pages/__init__.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/pages/test_invariant.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/pages/test_lifecycle.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/pages/test_ops.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/test_imports.py +0 -0
- {nuspace-0.2.0 → nuspace-0.2.1}/tests/nuspace/web/__init__.py +0 -0
|
@@ -2,13 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## Packages shipped from this repo
|
|
4
4
|
|
|
5
|
-
- **nuspace** (the runtime, the `nuspace` command) — 0.2.
|
|
6
|
-
- **nuspace-ui** (the compiled web bundle) — 0.2.
|
|
5
|
+
- **nuspace** (the runtime, the `nuspace` command) — 0.2.1
|
|
6
|
+
- **nuspace-ui** (the compiled web bundle) — 0.2.1
|
|
7
7
|
|
|
8
8
|
Below is the changelog for **nuspace** - the full commit stream. Newest first.
|
|
9
9
|
The `nuspace-ui` wheel is the same source tree's vite output and is not tracked
|
|
10
10
|
separately here.
|
|
11
11
|
|
|
12
|
+
## 0.2.1 — 2026-09-17
|
|
13
|
+
|
|
14
|
+
- Open a page on quiet air and its own name instead of decoration
|
|
15
|
+
- Type where you want a block instead of choosing what to make first
|
|
16
|
+
- Adding or deleting a block now leaves every other block on the page running
|
|
17
|
+
- Hovering a page block now reveals only that block's controls, and they hide again
|
|
18
|
+
- Slow the block controls' tooltips so passing over them says nothing
|
|
19
|
+
- Wipe a block's ui before it reruns, so its refs stop duplicating
|
|
20
|
+
- Give every block the same hover chrome: insert, drag, copy, code and status
|
|
21
|
+
- Follow nu 0.5.0's import split so apps and snippets keep resolving the fabrics
|
|
22
|
+
|
|
12
23
|
## 0.2.0 — 2026-09-15
|
|
13
24
|
|
|
14
25
|
- Root a snippet's refs under its own block, so an author never says where
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: nuspace
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Nu runtime built on Nu. A self-hosted programmable space that runs Nu apps in a tree of containers and applications.
|
|
5
5
|
Project-URL: Repository, https://github.com/nustackdev/nuspace
|
|
6
6
|
Author-email: Gor Arakelyan <gorarkln@gmail.com>
|
|
@@ -16,10 +16,10 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
17
|
Requires-Python: >=3.10
|
|
18
18
|
Requires-Dist: click>=8.0
|
|
19
|
-
Requires-Dist: nuagent>=0.1.
|
|
20
|
-
Requires-Dist: nucore>=0.
|
|
21
|
-
Requires-Dist: nuspace-ui>=0.2.
|
|
22
|
-
Requires-Dist: nustd[all]>=0.
|
|
19
|
+
Requires-Dist: nuagent>=0.1.3
|
|
20
|
+
Requires-Dist: nucore>=0.5.1
|
|
21
|
+
Requires-Dist: nuspace-ui>=0.2.1
|
|
22
|
+
Requires-Dist: nustd[all]>=0.5.1
|
|
23
23
|
Requires-Dist: rich-click>=1.6
|
|
24
24
|
Requires-Dist: rich>=10.7
|
|
25
25
|
Requires-Dist: typing-extensions>=4.7.0
|
|
@@ -30,7 +30,7 @@ Description-Content-Type: text/markdown
|
|
|
30
30
|
Nu runtime built on Nu. Self-hosted.
|
|
31
31
|
|
|
32
32
|
A tree of containers and applications.
|
|
33
|
-
The runtime evaluates Nu apps, provides the fabrics they use (
|
|
33
|
+
The runtime evaluates Nu apps, provides the fabrics they use (nustd.kv, nustd.proxy, nustd.mp_pool, nulog, nustd.ui), and renders a human interface for the ones that opt in.
|
|
34
34
|
|
|
35
35
|
## License
|
|
36
36
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Nu runtime built on Nu. Self-hosted.
|
|
4
4
|
|
|
5
5
|
A tree of containers and applications.
|
|
6
|
-
The runtime evaluates Nu apps, provides the fabrics they use (
|
|
6
|
+
The runtime evaluates Nu apps, provides the fabrics they use (nustd.kv, nustd.proxy, nustd.mp_pool, nulog, nustd.ui), and renders a human interface for the ones that opt in.
|
|
7
7
|
|
|
8
8
|
## License
|
|
9
9
|
|
|
@@ -34,7 +34,7 @@ ROOT = Path("/tmp/nuspace-apps-demo") # noqa: S108
|
|
|
34
34
|
# gets the same scope a section does, and `section` is its own id: an app is a
|
|
35
35
|
# section with no page. Editing `step` is what the demo edits.
|
|
36
36
|
COUNTER = '''import nu
|
|
37
|
-
import
|
|
37
|
+
import nustd.kv
|
|
38
38
|
from nuspace.core.shapes import Space
|
|
39
39
|
|
|
40
40
|
|
|
@@ -43,7 +43,7 @@ def out(section):
|
|
|
43
43
|
data = Space.state[section].data
|
|
44
44
|
now = nu.ToInt(data.get_item("ticks", nu.Str("0")))
|
|
45
45
|
tick = data.set_item("ticks", nu.ToStr(now + nu.Int({step})))
|
|
46
|
-
return
|
|
46
|
+
return nustd.kv.auto_flow_atomic(
|
|
47
47
|
data.set_item("ticks", nu.Str("0")) >> nu.ForeverDo(nu.DelayedDo(0.1, tick)),
|
|
48
48
|
scope=Space,
|
|
49
49
|
)
|
|
@@ -53,7 +53,7 @@ def out(section):
|
|
|
53
53
|
# processes, both reaching the one store through their own proxy, and the app
|
|
54
54
|
# being watched is named by id rather than by a path formatted into the source.
|
|
55
55
|
MIRROR = '''import nu
|
|
56
|
-
import
|
|
56
|
+
import nustd.kv
|
|
57
57
|
from nuspace.core.shapes import Space
|
|
58
58
|
|
|
59
59
|
WATCHED = "a_counter"
|
|
@@ -64,7 +64,7 @@ def out(section):
|
|
|
64
64
|
mine = Space.state[section].data
|
|
65
65
|
theirs = Space.state[WATCHED].data
|
|
66
66
|
copy = mine.set_item("seen", nu.ToStr(theirs.get_item("ticks", nu.Str("0"))))
|
|
67
|
-
return
|
|
67
|
+
return nustd.kv.auto_flow_atomic(nu.ForeverDo(nu.DelayedDo(0.1, copy)), scope=Space)
|
|
68
68
|
'''
|
|
69
69
|
|
|
70
70
|
|
|
@@ -35,14 +35,14 @@ import sys
|
|
|
35
35
|
from pathlib import Path
|
|
36
36
|
|
|
37
37
|
import nu
|
|
38
|
-
import
|
|
39
|
-
import
|
|
40
|
-
import
|
|
38
|
+
import nustd.kv
|
|
39
|
+
import nustd.mp_pool
|
|
40
|
+
import nustd.proxy
|
|
41
41
|
from nu.core.io import STDOUT
|
|
42
|
-
from nu.kv.fabrics import Navigator
|
|
43
42
|
from nuspace.apps import free_port, worker_init
|
|
44
43
|
from nuspace.core.shapes import Space
|
|
45
44
|
from nuspace.pages import ROOT_PAGE_ID, Runner, ops, page_tree
|
|
45
|
+
from nustd.kv.fabrics import Navigator
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
ROOT = Path("/tmp/nuspace-pages-demo") # noqa: S108
|
|
@@ -54,7 +54,7 @@ PAGE = "guides"
|
|
|
54
54
|
# row is reached by navigating -- `section` is the id this block runs under --
|
|
55
55
|
# so nothing here builds a key. Editing `step` is what the demo edits.
|
|
56
56
|
COUNTER = '''import nu
|
|
57
|
-
import
|
|
57
|
+
import nustd.kv
|
|
58
58
|
from nuspace.core.shapes import Space
|
|
59
59
|
|
|
60
60
|
|
|
@@ -63,7 +63,7 @@ def out(section):
|
|
|
63
63
|
data = Space.state[section].data
|
|
64
64
|
now = nu.ToInt(data.get_item("ticks", nu.Str("0")))
|
|
65
65
|
tick = data.set_item("ticks", nu.ToStr(now + nu.Int({step})))
|
|
66
|
-
return
|
|
66
|
+
return nustd.kv.auto_flow_atomic(
|
|
67
67
|
data.set_item("ticks", nu.Str("0")) >> nu.ForeverDo(nu.DelayedDo(0.1, tick)),
|
|
68
68
|
scope=Space,
|
|
69
69
|
)
|
|
@@ -76,7 +76,7 @@ def out(section):
|
|
|
76
76
|
# section's row is `Space.state["s_counter"]`, an id used as a key, not a path
|
|
77
77
|
# a python format hole built. Nothing is formatted into this source at all.
|
|
78
78
|
MIRROR = '''import nu
|
|
79
|
-
import
|
|
79
|
+
import nustd.kv
|
|
80
80
|
from nuspace.core.shapes import Space
|
|
81
81
|
|
|
82
82
|
WATCHED = "s_counter"
|
|
@@ -87,7 +87,7 @@ def out(section):
|
|
|
87
87
|
mine = Space.state[section].data
|
|
88
88
|
theirs = Space.state[WATCHED].data
|
|
89
89
|
copy = mine.set_item("seen", nu.ToStr(theirs.get_item("ticks", nu.Str("0"))))
|
|
90
|
-
return
|
|
90
|
+
return nustd.kv.auto_flow_atomic(nu.ForeverDo(nu.DelayedDo(0.1, copy)), scope=Space)
|
|
91
91
|
'''
|
|
92
92
|
|
|
93
93
|
|
|
@@ -155,10 +155,10 @@ def host(path, body):
|
|
|
155
155
|
"""
|
|
156
156
|
address = f"127.0.0.1:{free_port()}"
|
|
157
157
|
return nu.With(
|
|
158
|
-
|
|
158
|
+
nustd.kv.rocksdb_navigator(path),
|
|
159
159
|
nu.Provide(dict, {}),
|
|
160
160
|
nu.Provide(
|
|
161
|
-
|
|
161
|
+
nustd.proxy.InvisiblesServer,
|
|
162
162
|
{
|
|
163
163
|
"target": Navigator,
|
|
164
164
|
"address": address,
|
|
@@ -167,7 +167,7 @@ def host(path, body):
|
|
|
167
167
|
},
|
|
168
168
|
),
|
|
169
169
|
nu.Provide(
|
|
170
|
-
|
|
170
|
+
nustd.mp_pool.WorkerPool,
|
|
171
171
|
{"name": "nuspace-pages", "init": worker_init(address)},
|
|
172
172
|
),
|
|
173
173
|
body=body,
|
|
@@ -179,7 +179,7 @@ def demo():
|
|
|
179
179
|
ROOT.mkdir(parents=True, exist_ok=True)
|
|
180
180
|
tree = host(
|
|
181
181
|
str(ROOT / "db"),
|
|
182
|
-
|
|
182
|
+
nustd.kv.auto_flow_atomic(SEED, scope=Space)
|
|
183
183
|
>> page_tree(PAGE, alongside=SCRIPT, duration=15.0),
|
|
184
184
|
)
|
|
185
185
|
asyncio.run(nu.arun(tree, nu.Context(), max_parallel=64))
|
|
@@ -44,7 +44,7 @@ import sys
|
|
|
44
44
|
from pathlib import Path
|
|
45
45
|
|
|
46
46
|
import nu
|
|
47
|
-
import
|
|
47
|
+
import nustd.kv
|
|
48
48
|
from nuspace.core.shapes import Space
|
|
49
49
|
from nuspace.web import space_tree
|
|
50
50
|
|
|
@@ -58,7 +58,7 @@ def demo(fresh=True):
|
|
|
58
58
|
shutil.rmtree(ROOT, ignore_errors=True)
|
|
59
59
|
ROOT.mkdir(parents=True, exist_ok=True)
|
|
60
60
|
tree = space_tree(
|
|
61
|
-
|
|
61
|
+
nustd.kv.rocksdb_navigator(str(ROOT / "db"), tags=(Space,)),
|
|
62
62
|
store_tag=Space,
|
|
63
63
|
port=PORT,
|
|
64
64
|
)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nuspace"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "Nu runtime built on Nu. A self-hosted programmable space that runs Nu apps in a tree of containers and applications."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "AGPL-3.0-or-later"
|
|
@@ -16,13 +16,13 @@ dependencies = [
|
|
|
16
16
|
# ui for the browser, proxy (invisibles) for the worker's session, mp_pool
|
|
17
17
|
# for the workers themselves. Picking extras just means discovering the
|
|
18
18
|
# missing one at import time.
|
|
19
|
-
"nucore>=0.
|
|
20
|
-
"nustd[all]>=0.
|
|
19
|
+
"nucore>=0.5.1",
|
|
20
|
+
"nustd[all]>=0.5.1",
|
|
21
21
|
# The compiled web bundle. A separate distribution only because PyPI caps
|
|
22
22
|
# project size and a SPA rebuilt on every UI tweak would eat the quota --
|
|
23
23
|
# not optional. `pip install nuspace` gives you the browser.
|
|
24
|
-
"nuspace-ui>=0.2.
|
|
25
|
-
"nuagent>=0.1.
|
|
24
|
+
"nuspace-ui>=0.2.1",
|
|
25
|
+
"nuagent>=0.1.3",
|
|
26
26
|
"typing-extensions>=4.7.0",
|
|
27
27
|
"click>=8.0",
|
|
28
28
|
"rich>=10.7",
|
|
@@ -174,6 +174,8 @@ convention = "google"
|
|
|
174
174
|
known-first-party = [
|
|
175
175
|
"nuspace",
|
|
176
176
|
"nu",
|
|
177
|
+
"nustd",
|
|
178
|
+
"nucli",
|
|
177
179
|
]
|
|
178
180
|
force-single-line = false
|
|
179
181
|
lines-after-imports = 2
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""The model endpoint: a Claude Code prompt, fed the run's whole transcript.
|
|
2
2
|
|
|
3
3
|
nuagent's ``turn`` calls ``chat(messages=...)`` and expects a term yielding
|
|
4
|
-
``{"text": ...}``. ``
|
|
5
|
-
key; ``
|
|
4
|
+
``{"text": ...}``. ``nustd.llm`` speaks that natively but wants a provider and a
|
|
5
|
+
key; ``nustd.cc`` wants neither, and a space is a thing you run on your own
|
|
6
6
|
machine, so that is what is wired here. The cost is that each call is a fresh
|
|
7
7
|
Claude Code session, so the conversation has to ride along in the prompt --
|
|
8
8
|
which is what :class:`Rendered` is for.
|
|
@@ -29,7 +29,7 @@ from __future__ import annotations
|
|
|
29
29
|
from typing import TYPE_CHECKING
|
|
30
30
|
|
|
31
31
|
import nu
|
|
32
|
-
import
|
|
32
|
+
import nustd.cc
|
|
33
33
|
from nu.lang import ScalarQuery
|
|
34
34
|
from nu.lang.sentinels import EMPTY, INVALID
|
|
35
35
|
|
|
@@ -47,7 +47,7 @@ __all__ = ["Bot", "Rendered", "chat_with"]
|
|
|
47
47
|
class Bot(nu.Service):
|
|
48
48
|
"""The Claude Code endpoint one space's agent runs against."""
|
|
49
49
|
|
|
50
|
-
ask =
|
|
50
|
+
ask = nustd.cc.PromptRef.method()
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
class Rendered(ScalarQuery):
|
|
@@ -86,7 +86,7 @@ class Rendered(ScalarQuery):
|
|
|
86
86
|
def _render(messages: object) -> object:
|
|
87
87
|
"""Role-tagged blocks, or INVALID for anything that is not a transcript.
|
|
88
88
|
|
|
89
|
-
Duck-typed, and it has to be. A ``
|
|
89
|
+
Duck-typed, and it has to be. A ``nustd.kv`` ``ListRef`` of dicts does not
|
|
90
90
|
read back as ``list[dict]`` -- it reads back as a lazy sequence view whose
|
|
91
91
|
elements are mapping views, live cursors into the store. An
|
|
92
92
|
``isinstance(messages, list)`` gate therefore rejected every real
|
|
@@ -215,7 +215,7 @@ def messages_of(*, root: type[Shape] | None = None) -> nu.Nu:
|
|
|
215
215
|
def running(*, root: type[Shape] | None = None) -> nu.Nu:
|
|
216
216
|
"""Whether a run is in flight, as a term.
|
|
217
217
|
|
|
218
|
-
Read off the store rather than off a ``
|
|
218
|
+
Read off the store rather than off a ``nustd.mem`` record, unlike the apps
|
|
219
219
|
runner's ``attached``. An agent run is a fact about the space, not about
|
|
220
220
|
the process: the browser has to see it, and it is the same answer for
|
|
221
221
|
every connection.
|
|
@@ -18,7 +18,7 @@ prose around a fence and the person sees silence.
|
|
|
18
18
|
|
|
19
19
|
One section is nuspace's own, and it exists to *correct* the stock surface
|
|
20
20
|
preamble. That preamble tells the model to redeclare every Shape it touches,
|
|
21
|
-
because ``
|
|
21
|
+
because ``nustd.mem`` addresses by slot name and a matching declaration therefore
|
|
22
22
|
reaches the host's world. A space's store does not work that way: it is bound
|
|
23
23
|
``tags=(Space,)``, keyed on the class object, so a redeclared ``Space`` is a
|
|
24
24
|
different address and every write through it lands nowhere, silently. The
|
|
@@ -79,12 +79,13 @@ else -- by writing it:
|
|
|
79
79
|
|
|
80
80
|
```python
|
|
81
81
|
import nu
|
|
82
|
+
import nustd.mem
|
|
82
83
|
|
|
83
84
|
from nuspace.core.shapes import Space
|
|
84
85
|
|
|
85
86
|
|
|
86
87
|
class Run(nu.Shape):
|
|
87
|
-
done =
|
|
88
|
+
done = nustd.mem.BoolRef.slot()
|
|
88
89
|
|
|
89
90
|
|
|
90
91
|
def out():
|
|
@@ -151,7 +152,7 @@ lands in a store nobody reads. Import, never redeclare. The same goes for
|
|
|
151
152
|
name them at all.
|
|
152
153
|
|
|
153
154
|
**Do not bracket your program.** The host already holds the atomic bracket over
|
|
154
|
-
the store. No `
|
|
155
|
+
the store. No `nustd.kv.auto_flow_atomic`, no `nu.With`, no `nu.Provide`.
|
|
155
156
|
|
|
156
157
|
**Prefer the ops modules to hand-written ref chains.** Each function returns a
|
|
157
158
|
Nu term and fixes every invariant the store has -- a page's `parent` and its
|
|
@@ -184,16 +185,16 @@ runs under; ask for the ones you need and leave out the ones you do not:
|
|
|
184
185
|
|
|
185
186
|
```python
|
|
186
187
|
import nu
|
|
187
|
-
import
|
|
188
|
+
import nustd.ui
|
|
188
189
|
from nuspace.core.shapes import Space
|
|
189
190
|
|
|
190
191
|
|
|
191
192
|
def out(section):
|
|
192
|
-
heading =
|
|
193
|
+
heading = nustd.ui.TextRef("title")
|
|
193
194
|
return heading.set(nu.ToStr(Space.state[section].data.get_item("name", nu.Str("?"))))
|
|
194
195
|
```
|
|
195
196
|
|
|
196
|
-
**A block does not say where it renders.** Name a `
|
|
197
|
+
**A block does not say where it renders.** Name a `nustd.ui` Ref plainly, as
|
|
197
198
|
above, and nuspace roots it under this block on the page. `Space.state[id]` is
|
|
198
199
|
any block's own scratch row, which is also how one block reads another's: pass
|
|
199
200
|
the other block's id, never a key built out of a prefix.
|
|
@@ -33,7 +33,7 @@ the whole security model:
|
|
|
33
33
|
``space_tree``'s head. The model reaches it by importing the real ``Space``.
|
|
34
34
|
- an untagged ``dict`` is provided **here**, and it is where ``nuagent.Run``
|
|
35
35
|
lives. The model writes ``Run.done`` into it to end the run, and any
|
|
36
|
-
``
|
|
36
|
+
``nustd.mem`` scratch it invents lands there too, out of the way.
|
|
37
37
|
|
|
38
38
|
``Agent`` carries the session slots, so the conversation is in kv and the
|
|
39
39
|
sidebar subscribes to it directly. It is handed to nuagent as ``session``
|
|
@@ -48,8 +48,8 @@ from typing import TYPE_CHECKING
|
|
|
48
48
|
import nuagent
|
|
49
49
|
|
|
50
50
|
import nu
|
|
51
|
-
import
|
|
52
|
-
import
|
|
51
|
+
import nustd.cc
|
|
52
|
+
import nustd.kv
|
|
53
53
|
from nuspace._root import resolve_root
|
|
54
54
|
from nuspace.apps import ops as apps_ops
|
|
55
55
|
from nuspace.chat import ops as chat_ops
|
|
@@ -115,7 +115,7 @@ def agent_runner(
|
|
|
115
115
|
space at ollama or an api key instead -- or at a stub, which is
|
|
116
116
|
what makes the wiring testable without spending a model call.
|
|
117
117
|
bind: the bracket that provides whatever ``chat`` addresses. Must be
|
|
118
|
-
passed with ``chat``: the default pair is ``
|
|
118
|
+
passed with ``chat``: the default pair is ``nustd.cc.bind(Bot, ...)``
|
|
119
119
|
and a Service endpoint with nothing provided for it is a
|
|
120
120
|
``LookupError`` on the first turn, not at boot.
|
|
121
121
|
|
|
@@ -136,7 +136,7 @@ def agent_runner(
|
|
|
136
136
|
if chat is None:
|
|
137
137
|
chat = chat_with(system_prompt(root))
|
|
138
138
|
if bind is None:
|
|
139
|
-
bind =
|
|
139
|
+
bind = nustd.cc.bind(Bot, model=model, allowed_tools=[], permission_mode="default")
|
|
140
140
|
|
|
141
141
|
loop = nuagent.agent(
|
|
142
142
|
# `Space.agent` is the session: the six slots nuagent reads are
|
|
@@ -190,11 +190,11 @@ def agent_runner(
|
|
|
190
190
|
arm = arms.event(_RUN, agent.nonce.on_change(), once)
|
|
191
191
|
|
|
192
192
|
return nu.With(
|
|
193
|
-
# Untagged, so `nuagent.Run` -- and any
|
|
193
|
+
# Untagged, so `nuagent.Run` -- and any nustd.mem the model invents --
|
|
194
194
|
# resolves here and nowhere near the conversation.
|
|
195
195
|
nu.Provide(dict, {}),
|
|
196
196
|
bind,
|
|
197
|
-
body=
|
|
197
|
+
body=nustd.kv.auto_flow_atomic(boot >> arm, scope=root),
|
|
198
198
|
)
|
|
199
199
|
|
|
200
200
|
|
|
@@ -25,7 +25,7 @@ One run at a time, because nuagent is one run at a time. When that stops being
|
|
|
25
25
|
true this grows a ``ShapesDictRef`` keyed by run id and nothing else moves.
|
|
26
26
|
|
|
27
27
|
``KVRun`` is not used and neither is nuagent's ``Run``-on-the-session idea:
|
|
28
|
-
the model ends a run by writing ``Run.done``, which is ``
|
|
28
|
+
the model ends a run by writing ``Run.done``, which is ``nustd.mem`` and
|
|
29
29
|
addresses by slot name into the untagged store :mod:`nuspace.agent.runner`
|
|
30
30
|
provides around the loop. Keeping it off ``Agent`` is what keeps the model
|
|
31
31
|
from reaching the conversation.
|
|
@@ -34,7 +34,7 @@ from reaching the conversation.
|
|
|
34
34
|
from __future__ import annotations
|
|
35
35
|
|
|
36
36
|
import nu
|
|
37
|
-
import
|
|
37
|
+
import nustd.kv
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
__all__ = [
|
|
@@ -70,15 +70,15 @@ class Agent(nu.Shape):
|
|
|
70
70
|
"""One agent run: nuagent's session slots, plus how a human starts one."""
|
|
71
71
|
|
|
72
72
|
# -- the submission, nuspace's own ---------------------------------------
|
|
73
|
-
task =
|
|
74
|
-
nonce =
|
|
75
|
-
status =
|
|
76
|
-
error =
|
|
73
|
+
task = nustd.kv.StrRef.slot()
|
|
74
|
+
nonce = nustd.kv.IntRef.slot()
|
|
75
|
+
status = nustd.kv.StrRef.slot()
|
|
76
|
+
error = nustd.kv.StrRef.slot()
|
|
77
77
|
|
|
78
78
|
# -- nuagent's session contract, slot for slot ---------------------------
|
|
79
|
-
messages =
|
|
80
|
-
reply =
|
|
81
|
-
draft =
|
|
82
|
-
outcome =
|
|
83
|
-
observation =
|
|
84
|
-
turns =
|
|
79
|
+
messages = nustd.kv.ListRef.slot(dict)
|
|
80
|
+
reply = nustd.kv.StrRef.slot()
|
|
81
|
+
draft = nustd.kv.ProgramRef.slot()
|
|
82
|
+
outcome = nustd.kv.StrRef.slot()
|
|
83
|
+
observation = nustd.kv.StrRef.slot()
|
|
84
|
+
turns = nustd.kv.IntRef.slot()
|
|
@@ -11,9 +11,9 @@ from __future__ import annotations
|
|
|
11
11
|
from typing import TYPE_CHECKING
|
|
12
12
|
|
|
13
13
|
import nu
|
|
14
|
-
import nu.kv
|
|
15
|
-
import nu.mp_pool
|
|
16
14
|
import nu.prog
|
|
15
|
+
import nustd.kv
|
|
16
|
+
import nustd.mp_pool
|
|
17
17
|
from nuspace._root import resolve_root
|
|
18
18
|
from nuspace.core.host import DEFAULT_CHANNEL_PREFIX, free_port, host, worker_init
|
|
19
19
|
|
|
@@ -77,12 +77,12 @@ def app_body(*, root: type[Shape] | None = None) -> nu.Nu:
|
|
|
77
77
|
# Construction failures are written to the app's own row, not raised: a
|
|
78
78
|
# dispatched body has no waiter, so an uncaught error vanishes silently.
|
|
79
79
|
load = root.apps[_APP].snippet.load(scope=scope)
|
|
80
|
-
report =
|
|
80
|
+
report = nustd.kv.auto_flow_atomic(
|
|
81
81
|
root.state[_APP].error.set(nu.ToStr(nu.AttrRef("error"))),
|
|
82
82
|
scope=root,
|
|
83
83
|
)
|
|
84
84
|
return nu.TryCatch(
|
|
85
|
-
nu.prog.Eval(
|
|
85
|
+
nu.prog.Eval(nustd.kv.auto_flow_atomic(load, scope=root)),
|
|
86
86
|
catch=report,
|
|
87
87
|
errors=nu.prog.ConstructionError,
|
|
88
88
|
)
|
|
@@ -97,7 +97,7 @@ def reconcile(*, root: type[Shape] | None = None) -> nu.Nu:
|
|
|
97
97
|
without the first app blocking the rest.
|
|
98
98
|
"""
|
|
99
99
|
root = resolve_root(root)
|
|
100
|
-
pool =
|
|
100
|
+
pool = nustd.mp_pool.PoolRef()
|
|
101
101
|
stop = nu.IfDo(
|
|
102
102
|
Runner.workers.contains(_APP),
|
|
103
103
|
pool.kill(Runner.workers[_APP]) >> Runner.workers.del_item(_APP),
|
|
@@ -182,7 +182,7 @@ def supervisor(
|
|
|
182
182
|
flow = live if alongside is None else (live | alongside)
|
|
183
183
|
if duration is not None:
|
|
184
184
|
flow = nu.Race(flow, nu.DelayedDo(nu.Float(duration), nu.Noop()))
|
|
185
|
-
return
|
|
185
|
+
return nustd.kv.auto_flow_atomic(seed >> flow, scope=root)
|
|
186
186
|
|
|
187
187
|
|
|
188
188
|
def apps_store(
|
|
@@ -193,8 +193,10 @@ def apps_store(
|
|
|
193
193
|
) -> nu.Nu:
|
|
194
194
|
"""The navigator bracket for a space on disk, with or without redis."""
|
|
195
195
|
if redis_url is None:
|
|
196
|
-
return
|
|
197
|
-
return
|
|
196
|
+
return nustd.kv.rocksdb_navigator(path)
|
|
197
|
+
return nustd.kv.rocksdb_navigator_redis(
|
|
198
|
+
path, redis_url=redis_url, channel_prefix=channel_prefix
|
|
199
|
+
)
|
|
198
200
|
|
|
199
201
|
|
|
200
202
|
def apps_tree(
|
|
@@ -8,8 +8,8 @@ This module imports nothing from :mod:`nuspace.core`, which is what keeps
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import nu
|
|
11
|
-
import
|
|
12
|
-
import
|
|
11
|
+
import nustd.kv
|
|
12
|
+
import nustd.mem
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
__all__ = ["DEFAULT_POLICY", "App", "Runner"]
|
|
@@ -30,22 +30,22 @@ class App(nu.Shape):
|
|
|
30
30
|
|
|
31
31
|
# An app is headless -- it produces, a page displays -- so nothing roots
|
|
32
32
|
# the refs it names and it has nowhere to render.
|
|
33
|
-
name =
|
|
34
|
-
snippet =
|
|
33
|
+
name = nustd.kv.StrRef.slot()
|
|
34
|
+
snippet = nustd.kv.ProgramRef.slot()
|
|
35
35
|
# Metadata. The policy engine slots in behind this, not beside it.
|
|
36
|
-
policy =
|
|
36
|
+
policy = nustd.kv.StrRef.slot()
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
class Runner(nu.Shape):
|
|
40
40
|
"""What the host process knows about who is running what.
|
|
41
41
|
|
|
42
|
-
``
|
|
42
|
+
``nustd.mem`` deliberately: this is host-local bookkeeping, and a kv write
|
|
43
43
|
here would let the driver wake itself. Pool ids are never reused, so "the
|
|
44
44
|
worker id changed" is a sound test for "that app restarted".
|
|
45
45
|
"""
|
|
46
46
|
|
|
47
|
-
workers =
|
|
47
|
+
workers = nustd.mem.DictRef.slot(int)
|
|
48
48
|
# Written by the runner's boot pass and by nothing else. Mem, so it reads
|
|
49
49
|
# True only where the runner itself is: a web driver in the same process
|
|
50
50
|
# sees it, one in another process never can, and neither has to be told.
|
|
51
|
-
attached =
|
|
51
|
+
attached = nustd.mem.BoolRef.slot()
|
|
@@ -31,7 +31,7 @@ one call the model can write without looking anything up::
|
|
|
31
31
|
from __future__ import annotations
|
|
32
32
|
|
|
33
33
|
import nu
|
|
34
|
-
import
|
|
34
|
+
import nustd.kv
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
__all__ = ["ROLES", "ROLE_AGENT", "ROLE_SYSTEM", "ROLE_USER", "Chat"]
|
|
@@ -53,4 +53,4 @@ ROLES = (ROLE_USER, ROLE_AGENT, ROLE_SYSTEM)
|
|
|
53
53
|
class Chat(nu.Shape):
|
|
54
54
|
"""One conversation. A list of ``{role, text}``, oldest first."""
|
|
55
55
|
|
|
56
|
-
messages =
|
|
56
|
+
messages = nustd.kv.ListRef.slot(dict)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
No commands yet. The CLI is a DX layer that will grow once the core
|
|
4
4
|
primitives settle. External automation should attach to a running space
|
|
5
|
-
via ``
|
|
5
|
+
via ``nustd.proxy`` instead of via bespoke CLI commands.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
@@ -17,11 +17,11 @@ import socket
|
|
|
17
17
|
from typing import TYPE_CHECKING
|
|
18
18
|
|
|
19
19
|
import nu
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
import
|
|
20
|
+
import nustd.kv
|
|
21
|
+
import nustd.mp_pool
|
|
22
|
+
import nustd.proxy
|
|
23
23
|
from nu.core.reactive import ObserverProtocol
|
|
24
|
-
from
|
|
24
|
+
from nustd.kv.fabrics import InMemoryObserver, InMemoryTransport, Navigator
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
if TYPE_CHECKING:
|
|
@@ -138,12 +138,12 @@ def worker_init(
|
|
|
138
138
|
"""
|
|
139
139
|
observer: tuple[nu.Nu, ...] = ()
|
|
140
140
|
if observer_address is not None:
|
|
141
|
-
observer = (
|
|
141
|
+
observer = (nustd.kv.proxy_observer(observer_address),)
|
|
142
142
|
elif redis_url is not None:
|
|
143
|
-
observer = (
|
|
143
|
+
observer = (nustd.kv.redis_observer(redis_url=redis_url, channel_prefix=channel_prefix),)
|
|
144
144
|
return nu.With(
|
|
145
145
|
*observer,
|
|
146
|
-
|
|
146
|
+
nustd.proxy.InvisiblesProxy(Navigator, address=address),
|
|
147
147
|
nu.Provide(dict, {}),
|
|
148
148
|
)
|
|
149
149
|
|
|
@@ -156,7 +156,7 @@ def served_navigator(address: str, *, store_tag: object = None) -> nu.Provide:
|
|
|
156
156
|
tag here is a ``LookupError`` before the server ever boots.
|
|
157
157
|
"""
|
|
158
158
|
return nu.Provide(
|
|
159
|
-
|
|
159
|
+
nustd.proxy.InvisiblesServer,
|
|
160
160
|
{
|
|
161
161
|
"target": Navigator,
|
|
162
162
|
"target_tag": store_tag,
|
|
@@ -172,10 +172,10 @@ def served_observer(address: str) -> nu.With:
|
|
|
172
172
|
|
|
173
173
|
The store's observer sees every write this process makes, and a worker on
|
|
174
174
|
the other end of a proxied Navigator sees none of them. This is the ears:
|
|
175
|
-
bind it here, bind :func:`
|
|
175
|
+
bind it here, bind :func:`nustd.kv.proxy_observer` in the worker, and a page
|
|
176
176
|
can be told something changed instead of being killed to find out.
|
|
177
177
|
"""
|
|
178
|
-
return
|
|
178
|
+
return nustd.kv.served_observer(address)
|
|
179
179
|
|
|
180
180
|
|
|
181
181
|
def worker_pool(
|
|
@@ -192,7 +192,7 @@ def worker_pool(
|
|
|
192
192
|
provides one has decided how long those processes live.
|
|
193
193
|
"""
|
|
194
194
|
return nu.Provide(
|
|
195
|
-
|
|
195
|
+
nustd.mp_pool.WorkerPool,
|
|
196
196
|
{
|
|
197
197
|
"name": name,
|
|
198
198
|
"init": worker_init(
|