@treenity/mods 3.0.1 → 3.0.2
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/board/view.tsx +1 -1
- package/brahman/helpers.ts +7 -7
- package/brahman/service.ts +24 -24
- package/brahman/types.ts +21 -21
- package/brahman/views/action-cards.tsx +33 -23
- package/brahman/views/bot-view.tsx +3 -2
- package/brahman/views/chat-editor.tsx +119 -124
- package/brahman/views/menu-editor.tsx +75 -89
- package/brahman/views/page-layout.tsx +10 -8
- package/brahman/views/tstring-input.tsx +25 -15
- package/canary/service.ts +18 -18
- package/dist/board/view.js +1 -1
- package/dist/board/view.js.map +1 -1
- package/dist/brahman/helpers.d.ts +1 -1
- package/dist/brahman/helpers.d.ts.map +1 -1
- package/dist/brahman/helpers.js +6 -6
- package/dist/brahman/helpers.js.map +1 -1
- package/dist/brahman/service.js +24 -24
- package/dist/brahman/service.js.map +1 -1
- package/dist/brahman/types.d.ts +1 -1
- package/dist/brahman/types.d.ts.map +1 -1
- package/dist/brahman/types.js +21 -21
- package/dist/brahman/types.js.map +1 -1
- package/dist/brahman/views/action-cards.d.ts.map +1 -1
- package/dist/brahman/views/action-cards.js +7 -4
- package/dist/brahman/views/action-cards.js.map +1 -1
- package/dist/brahman/views/bot-view.d.ts.map +1 -1
- package/dist/brahman/views/bot-view.js +2 -1
- package/dist/brahman/views/bot-view.js.map +1 -1
- package/dist/brahman/views/chat-editor.d.ts.map +1 -1
- package/dist/brahman/views/chat-editor.js +27 -18
- package/dist/brahman/views/chat-editor.js.map +1 -1
- package/dist/brahman/views/menu-editor.d.ts.map +1 -1
- package/dist/brahman/views/menu-editor.js +12 -16
- package/dist/brahman/views/menu-editor.js.map +1 -1
- package/dist/brahman/views/page-layout.d.ts.map +1 -1
- package/dist/brahman/views/page-layout.js +1 -1
- package/dist/brahman/views/page-layout.js.map +1 -1
- package/dist/brahman/views/tstring-input.d.ts.map +1 -1
- package/dist/brahman/views/tstring-input.js +7 -3
- package/dist/brahman/views/tstring-input.js.map +1 -1
- package/dist/canary/service.js +18 -18
- package/dist/canary/service.js.map +1 -1
- package/dist/doc/fs-codec.js +1 -1
- package/dist/doc/fs-codec.js.map +1 -1
- package/dist/doc/renderers.d.ts.map +1 -1
- package/dist/doc/renderers.js +2 -1
- package/dist/doc/renderers.js.map +1 -1
- package/dist/doc/toolbar.d.ts.map +1 -1
- package/dist/doc/toolbar.js +5 -5
- package/dist/doc/toolbar.js.map +1 -1
- package/dist/launcher/types.js +2 -2
- package/dist/launcher/types.js.map +1 -1
- package/dist/launcher/view.js +2 -2
- package/dist/launcher/view.js.map +1 -1
- package/dist/mindmap/branch.d.ts +10 -0
- package/dist/mindmap/branch.d.ts.map +1 -1
- package/dist/mindmap/branch.js +42 -9
- package/dist/mindmap/branch.js.map +1 -1
- package/dist/mindmap/sidebar.d.ts.map +1 -1
- package/dist/mindmap/sidebar.js +4 -3
- package/dist/mindmap/sidebar.js.map +1 -1
- package/dist/mindmap/view.d.ts.map +1 -1
- package/dist/mindmap/view.js +35 -4
- package/dist/mindmap/view.js.map +1 -1
- package/dist/sensor-demo/service.js +6 -5
- package/dist/sensor-demo/service.js.map +1 -1
- package/dist/sensor-generator/action.js +1 -1
- package/dist/sensor-generator/action.js.map +1 -1
- package/dist/sim/service.js +41 -41
- package/dist/sim/service.js.map +1 -1
- package/dist/table/view.js.map +1 -1
- package/dist/todo/types.js +2 -2
- package/dist/todo/types.js.map +1 -1
- package/dist/todo/view.js +6 -4
- package/dist/todo/view.js.map +1 -1
- package/dist/whisper/inbox.js +3 -3
- package/dist/whisper/inbox.js.map +1 -1
- package/dist/whisper/route.d.ts +1 -1
- package/dist/whisper/route.d.ts.map +1 -1
- package/dist/whisper/route.js +13 -13
- package/dist/whisper/route.js.map +1 -1
- package/doc/CLAUDE.md +1 -1
- package/doc/fs-codec.ts +1 -1
- package/doc/renderers.tsx +4 -3
- package/doc/toolbar.tsx +12 -9
- package/launcher/types.ts +2 -2
- package/launcher/view.tsx +12 -8
- package/mindmap/branch.tsx +121 -22
- package/mindmap/mindmap.css +52 -0
- package/mindmap/sidebar.tsx +9 -6
- package/mindmap/view.tsx +40 -4
- package/package.json +27 -3
- package/sensor-demo/service.ts +6 -5
- package/sensor-generator/action.ts +1 -1
- package/sim/service.ts +41 -41
- package/table/view.tsx +7 -2
- package/todo/types.ts +2 -2
- package/todo/view.tsx +9 -10
- package/whisper/inbox.ts +3 -3
- package/whisper/route.ts +13 -13
- package/board/board.test.ts +0 -212
- package/brahman/brahman.test.ts +0 -855
- package/doc/fs-codec.test.ts +0 -119
- package/doc/markdown.test.ts +0 -152
- package/sim/sim.test.ts +0 -282
package/mindmap/mindmap.css
CHANGED
|
@@ -99,6 +99,58 @@
|
|
|
99
99
|
cursor: pointer;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/* ── Add child button ── */
|
|
103
|
+
|
|
104
|
+
.mm-add-btn {
|
|
105
|
+
opacity: 0;
|
|
106
|
+
cursor: pointer;
|
|
107
|
+
transition: opacity 0.15s;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.mm-branch:hover > .mm-add-btn {
|
|
111
|
+
opacity: 1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.mm-add-bg {
|
|
115
|
+
fill: var(--surface-2);
|
|
116
|
+
stroke: var(--border);
|
|
117
|
+
stroke-width: 1;
|
|
118
|
+
transition: fill 0.15s;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.mm-add-btn:hover .mm-add-bg {
|
|
122
|
+
fill: var(--accent);
|
|
123
|
+
stroke: var(--accent);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.mm-add-icon {
|
|
127
|
+
font-size: 12px;
|
|
128
|
+
font-weight: 700;
|
|
129
|
+
fill: var(--text-3);
|
|
130
|
+
font-family: var(--font);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.mm-add-btn:hover .mm-add-icon {
|
|
134
|
+
fill: var(--bg);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* ── Inline input ── */
|
|
138
|
+
|
|
139
|
+
.mm-inline-input {
|
|
140
|
+
width: 100%;
|
|
141
|
+
height: 100%;
|
|
142
|
+
background: var(--surface);
|
|
143
|
+
border: 1.5px solid var(--accent);
|
|
144
|
+
border-radius: 4px;
|
|
145
|
+
color: var(--text);
|
|
146
|
+
font-family: var(--font);
|
|
147
|
+
font-size: 13px;
|
|
148
|
+
font-weight: 500;
|
|
149
|
+
padding: 0 6px;
|
|
150
|
+
outline: none;
|
|
151
|
+
box-sizing: border-box;
|
|
152
|
+
}
|
|
153
|
+
|
|
102
154
|
/* ── Toolbar ── */
|
|
103
155
|
|
|
104
156
|
.mm-toolbar {
|
package/mindmap/sidebar.tsx
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { execute, usePath } from '@treenity/react/hooks';
|
|
5
5
|
import { getActions, getComponents, getPlainFields, getSchema } from '@treenity/react/mods/editor-ui/node-utils';
|
|
6
|
+
import { Button } from '@treenity/react/ui/button';
|
|
6
7
|
|
|
7
8
|
type Props = {
|
|
8
9
|
path: string;
|
|
@@ -22,8 +23,8 @@ export function MindMapSidebar({ path, onClose, onNavigate }: Props) {
|
|
|
22
23
|
return (
|
|
23
24
|
<div className="mm-sidebar">
|
|
24
25
|
<div className="mm-sidebar-header">
|
|
25
|
-
<span className="text-
|
|
26
|
-
<
|
|
26
|
+
<span className="text-muted-foreground">Loading...</span>
|
|
27
|
+
<Button variant="ghost" size="sm" className="mm-btn" onClick={onClose}>×</Button>
|
|
27
28
|
</div>
|
|
28
29
|
</div>
|
|
29
30
|
);
|
|
@@ -45,9 +46,9 @@ export function MindMapSidebar({ path, onClose, onNavigate }: Props) {
|
|
|
45
46
|
>
|
|
46
47
|
{path.split('/').at(-1) || '/'}
|
|
47
48
|
</span>
|
|
48
|
-
<span className="text-[11px] text-
|
|
49
|
+
<span className="text-[11px] text-muted-foreground truncate">{path}</span>
|
|
49
50
|
</div>
|
|
50
|
-
<
|
|
51
|
+
<Button variant="ghost" size="sm" className="mm-btn shrink-0" onClick={onClose}>×</Button>
|
|
51
52
|
</div>
|
|
52
53
|
|
|
53
54
|
<div className="mm-sidebar-body">
|
|
@@ -110,13 +111,15 @@ export function MindMapSidebar({ path, onClose, onNavigate }: Props) {
|
|
|
110
111
|
<div className="mm-section-label">Actions</div>
|
|
111
112
|
<div className="flex flex-wrap gap-1">
|
|
112
113
|
{actions.map(a => (
|
|
113
|
-
<
|
|
114
|
+
<Button
|
|
114
115
|
key={a}
|
|
116
|
+
variant="ghost"
|
|
117
|
+
size="sm"
|
|
115
118
|
className="mm-action-btn"
|
|
116
119
|
onClick={() => execute(path, a).catch(console.error)}
|
|
117
120
|
>
|
|
118
121
|
{a}
|
|
119
|
-
</
|
|
122
|
+
</Button>
|
|
120
123
|
))}
|
|
121
124
|
</div>
|
|
122
125
|
</div>
|
package/mindmap/view.tsx
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
// MindMap View — Miro-style horizontal tree with organic curves
|
|
2
2
|
// Each node fetches its own children via useChildren when expanded
|
|
3
3
|
|
|
4
|
+
import type { NodeData } from '@treenity/core';
|
|
4
5
|
import { register } from '@treenity/core';
|
|
5
6
|
import type { View } from '@treenity/react/context';
|
|
6
7
|
import { useChildren } from '@treenity/react/hooks';
|
|
8
|
+
import { trpc } from '@treenity/react/trpc';
|
|
7
9
|
import { select } from 'd3-selection';
|
|
8
10
|
import 'd3-transition';
|
|
9
11
|
import { zoom as d3zoom, type ZoomBehavior, zoomIdentity } from 'd3-zoom';
|
|
10
12
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
11
|
-
import { MindMapBranch, MindMapCtx, type MindMapState } from './branch';
|
|
13
|
+
import { type EditingAt, MindMapBranch, MindMapCtx, type MindMapState } from './branch';
|
|
12
14
|
import { MindMapSidebar } from './sidebar';
|
|
13
15
|
import type { MindMapConfig } from './types';
|
|
14
16
|
import './mindmap.css';
|
|
@@ -38,6 +40,7 @@ const MindMapView: View<MindMapConfig> = ({ value, ctx }) => {
|
|
|
38
40
|
|
|
39
41
|
const [expanded, setExpanded] = useState<Set<string>>(() => new Set([rootPath]));
|
|
40
42
|
const [selectedPath, setSelectedPath] = useState<string | null>(null);
|
|
43
|
+
const [editingAt, setEditingAt] = useState<EditingAt>(null);
|
|
41
44
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
42
45
|
const svgRef = useRef<SVGSVGElement>(null);
|
|
43
46
|
const gRef = useRef<SVGGElement>(null);
|
|
@@ -117,19 +120,52 @@ const MindMapView: View<MindMapConfig> = ({ value, ctx }) => {
|
|
|
117
120
|
window.dispatchEvent(new PopStateEvent('popstate'));
|
|
118
121
|
}, []);
|
|
119
122
|
|
|
123
|
+
const handleAddChild = useCallback((parentPath: string, side: 'left' | 'right', color: string) => {
|
|
124
|
+
setEditingAt({ parentPath, side, color });
|
|
125
|
+
setExpanded(prev => new Set([...prev, parentPath]));
|
|
126
|
+
}, []);
|
|
127
|
+
|
|
128
|
+
const handleCommitAdd = useCallback((parentPath: string, name: string) => {
|
|
129
|
+
const childPath = `${parentPath}/${name}`;
|
|
130
|
+
trpc.set.mutate({ node: { $path: childPath, $type: 'dir' } as NodeData });
|
|
131
|
+
setEditingAt(null);
|
|
132
|
+
}, []);
|
|
133
|
+
|
|
134
|
+
const handleCancelAdd = useCallback(() => setEditingAt(null), []);
|
|
135
|
+
|
|
136
|
+
const handleDelete = useCallback((path: string) => {
|
|
137
|
+
if (path === rootPath) return;
|
|
138
|
+
if (!confirm(`Delete ${basename(path)}?`)) return;
|
|
139
|
+
trpc.remove.mutate({ path });
|
|
140
|
+
setSelectedPath(null);
|
|
141
|
+
}, [rootPath]);
|
|
142
|
+
|
|
120
143
|
useEffect(() => {
|
|
121
144
|
const handler = (e: KeyboardEvent) => {
|
|
122
145
|
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
|
|
123
146
|
if (e.key === 'Enter' && selectedPath) { e.preventDefault(); handleToggle(selectedPath); }
|
|
124
147
|
if (e.key === 'Escape') setSelectedPath(null);
|
|
148
|
+
if ((e.key === 'Delete' || e.key === 'Backspace') && selectedPath && selectedPath !== rootPath) {
|
|
149
|
+
e.preventDefault();
|
|
150
|
+
handleDelete(selectedPath);
|
|
151
|
+
}
|
|
152
|
+
if (e.key === 'Tab' && selectedPath && !editingAt) {
|
|
153
|
+
e.preventDefault();
|
|
154
|
+
handleAddChild(selectedPath, 'right', PALETTE[0]);
|
|
155
|
+
}
|
|
125
156
|
};
|
|
126
157
|
window.addEventListener('keydown', handler);
|
|
127
158
|
return () => window.removeEventListener('keydown', handler);
|
|
128
|
-
}, [selectedPath, handleToggle]);
|
|
159
|
+
}, [selectedPath, handleToggle, handleDelete, handleAddChild, rootPath, editingAt]);
|
|
129
160
|
|
|
130
161
|
const mmState: MindMapState = useMemo(
|
|
131
|
-
() => ({
|
|
132
|
-
|
|
162
|
+
() => ({
|
|
163
|
+
expanded, selectedPath, editingAt,
|
|
164
|
+
onToggle: handleToggle, onSelect: handleSelect,
|
|
165
|
+
onAddChild: handleAddChild, onCommitAdd: handleCommitAdd, onCancelAdd: handleCancelAdd,
|
|
166
|
+
onDelete: handleDelete,
|
|
167
|
+
}),
|
|
168
|
+
[expanded, selectedPath, editingAt, handleToggle, handleSelect, handleAddChild, handleCommitAdd, handleCancelAdd, handleDelete],
|
|
133
169
|
);
|
|
134
170
|
|
|
135
171
|
const svgW = selectedPath ? dims.w - 280 : dims.w;
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@treenity/mods",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "Official Treenity modules — board, sim, cafe, doc, mindmap, and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
9
|
-
"license": "
|
|
9
|
+
"license": "FSL-1.1-MIT",
|
|
10
10
|
"author": "Treenity <hello@treenity.ai> (https://treenity.ai)",
|
|
11
11
|
"homepage": "https://github.com/treenity-ai/treenity",
|
|
12
12
|
"repository": {
|
|
@@ -53,19 +53,43 @@
|
|
|
53
53
|
"three",
|
|
54
54
|
"todo",
|
|
55
55
|
"whisper",
|
|
56
|
+
"!**/*.test.ts",
|
|
57
|
+
"!**/*.test.tsx",
|
|
56
58
|
"LICENSE"
|
|
57
59
|
],
|
|
58
60
|
"scripts": {
|
|
59
61
|
"build": "tsc -p tsconfig.build.json",
|
|
60
62
|
"prepack": "npm run build"
|
|
61
63
|
},
|
|
64
|
+
"imports": {
|
|
65
|
+
"#*": {
|
|
66
|
+
"development": [
|
|
67
|
+
"./*.ts",
|
|
68
|
+
"./*.tsx"
|
|
69
|
+
],
|
|
70
|
+
"default": [
|
|
71
|
+
"./dist/*.js"
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"@grammyjs/runner": "^2.0.3"
|
|
77
|
+
},
|
|
78
|
+
"optionalDependencies": {
|
|
79
|
+
"@huggingface/transformers": "^3.8.1",
|
|
80
|
+
"wavefile": "^11.0.0"
|
|
81
|
+
},
|
|
62
82
|
"peerDependencies": {
|
|
63
83
|
"@treenity/core": "^3.0.0",
|
|
64
|
-
"@treenity/react": "^3.0.0"
|
|
84
|
+
"@treenity/react": "^3.0.0",
|
|
85
|
+
"grammy": "^1.39.0"
|
|
65
86
|
},
|
|
66
87
|
"peerDependenciesMeta": {
|
|
67
88
|
"@treenity/react": {
|
|
68
89
|
"optional": true
|
|
90
|
+
},
|
|
91
|
+
"grammy": {
|
|
92
|
+
"optional": true
|
|
69
93
|
}
|
|
70
94
|
},
|
|
71
95
|
"devDependencies": {
|
package/sensor-demo/service.ts
CHANGED
|
@@ -2,26 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
import { createNode, register } from '@treenity/core';
|
|
4
4
|
import '@treenity/core/contexts/service';
|
|
5
|
+
import { safeInterval } from '@treenity/core/util/safe-timers';
|
|
5
6
|
import { SensorReading } from './types';
|
|
6
7
|
|
|
7
8
|
register('examples.demo.sensor', 'service', async (node, ctx) => {
|
|
8
9
|
let seq = 0;
|
|
9
10
|
const MAX = 10;
|
|
10
|
-
const timer =
|
|
11
|
+
const timer = safeInterval(async () => {
|
|
11
12
|
const ts = Date.now();
|
|
12
13
|
const name = String(ts).slice(-6).padStart(6, '0');
|
|
13
|
-
await ctx.
|
|
14
|
+
await ctx.tree.set(createNode(`${node.$path}/${name}`, SensorReading, {
|
|
14
15
|
ts,
|
|
15
16
|
value: +(20 + Math.sin(seq * 0.1) * 5 + Math.random() * 2).toFixed(1),
|
|
16
17
|
seq: seq++,
|
|
17
18
|
}));
|
|
18
19
|
// Trim old readings
|
|
19
|
-
const { items } = await ctx.
|
|
20
|
+
const { items } = await ctx.tree.getChildren(node.$path);
|
|
20
21
|
if (items.length > MAX) {
|
|
21
22
|
items.sort((a, b) => (a.ts as number) - (b.ts as number));
|
|
22
|
-
for (const old of items.slice(0, items.length - MAX)) await ctx.
|
|
23
|
+
for (const old of items.slice(0, items.length - MAX)) await ctx.tree.remove(old.$path);
|
|
23
24
|
}
|
|
24
|
-
}, 1000);
|
|
25
|
+
}, 1000, 'sensor-demo.tick');
|
|
25
26
|
console.log(`[sensor-demo] started on ${node.$path}`);
|
|
26
27
|
return { stop: async () => clearInterval(timer) };
|
|
27
28
|
});
|
package/sim/service.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
resolve,
|
|
15
15
|
} from '@treenity/core';
|
|
16
16
|
import '@treenity/core/contexts/service';
|
|
17
|
-
import {
|
|
17
|
+
import { newComponent, setComponent } from '@treenity/core/comp';
|
|
18
18
|
import { type ActionCtx, serverNodeHandle } from '@treenity/core/server/actions';
|
|
19
19
|
import {
|
|
20
20
|
type AgentEvent,
|
|
@@ -108,7 +108,7 @@ function getAgentEvents(n: NodeData): AgentEvent[] {
|
|
|
108
108
|
function pushAgentEvent(n: NodeData, event: AgentEvent) {
|
|
109
109
|
const ev = getComponent(n, SimEvents);
|
|
110
110
|
const entries = [...(ev?.entries ?? []), event].slice(-30);
|
|
111
|
-
|
|
111
|
+
setComponent(n, SimEvents, { entries });
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
// ── LLM tools — base + interact ──
|
|
@@ -281,13 +281,13 @@ function mockThink(agent: NodeData, all: NodeData[]): Tool[] {
|
|
|
281
281
|
/** @description Start the agent simulation loop */
|
|
282
282
|
register('sim.world', 'action:start', async (ctx: ActionCtx) => {
|
|
283
283
|
const cfg = getComponent(ctx.node, SimConfig)!;
|
|
284
|
-
|
|
284
|
+
setComponent(ctx.node, SimConfig, { ...cfg, running: true });
|
|
285
285
|
}, { description: 'Start the simulation' });
|
|
286
286
|
|
|
287
287
|
/** @description Stop the agent simulation loop */
|
|
288
288
|
register('sim.world', 'action:stop', async (ctx: ActionCtx) => {
|
|
289
289
|
const cfg = getComponent(ctx.node, SimConfig)!;
|
|
290
|
-
|
|
290
|
+
setComponent(ctx.node, SimConfig, { ...cfg, running: false });
|
|
291
291
|
}, { description: 'Stop the simulation' });
|
|
292
292
|
|
|
293
293
|
/** @description Update simulation settings (roundDelay, model, dimensions) */
|
|
@@ -298,7 +298,7 @@ register('sim.world', 'action:set-config', async (ctx: ActionCtx, params: any) =
|
|
|
298
298
|
if (params.model !== undefined) next.model = String(params.model);
|
|
299
299
|
if (params.width !== undefined) next.width = Number(params.width);
|
|
300
300
|
if (params.height !== undefined) next.height = Number(params.height);
|
|
301
|
-
|
|
301
|
+
setComponent(ctx.node, SimConfig, next);
|
|
302
302
|
}, { description: 'Update simulation settings', params: 'roundDelay?, model?, width?, height?' });
|
|
303
303
|
|
|
304
304
|
/** @description Add an agent or item entity to the simulation world */
|
|
@@ -307,23 +307,23 @@ register('sim.world', 'action:add-entity', async (ctx: ActionCtx, params: any) =
|
|
|
307
307
|
const type = params.type || 'sim.item';
|
|
308
308
|
const path = `${ctx.node.$path}/${id}`;
|
|
309
309
|
const components: Record<string, any> = {
|
|
310
|
-
descriptive:
|
|
310
|
+
descriptive: newComponent(SimDescriptive, {
|
|
311
311
|
name: params.name || id,
|
|
312
312
|
icon: params.icon || '?',
|
|
313
313
|
description: params.description || '',
|
|
314
314
|
}),
|
|
315
|
-
position:
|
|
315
|
+
position: newComponent(SimPosition, {
|
|
316
316
|
x: params.x ?? 300,
|
|
317
317
|
y: params.y ?? 200,
|
|
318
318
|
radius: params.radius ?? 100,
|
|
319
319
|
}),
|
|
320
320
|
};
|
|
321
321
|
if (type === 'sim.agent') {
|
|
322
|
-
components.ai =
|
|
323
|
-
components.memory =
|
|
324
|
-
components.events =
|
|
322
|
+
components.ai = newComponent(SimAi, { systemPrompt: params.systemPrompt || `You are ${params.name}.` });
|
|
323
|
+
components.memory = newComponent(SimMemory, { entries: [] });
|
|
324
|
+
components.events = newComponent(SimEvents, { entries: [] });
|
|
325
325
|
}
|
|
326
|
-
await ctx.
|
|
326
|
+
await ctx.tree.set(createNode(path, type, {}, components));
|
|
327
327
|
}, { description: 'Add agent or item to the world', params: 'name, icon, type?, x?, y?, radius?, systemPrompt?, description?' });
|
|
328
328
|
|
|
329
329
|
// Agent actions (callable by other agents via interact, or by UI)
|
|
@@ -333,18 +333,18 @@ register('sim.agent', 'action:move', async (ctx: ActionCtx, params: any) => {
|
|
|
333
333
|
const next = { ...pos };
|
|
334
334
|
if (params.x !== undefined) next.x = Math.round(Number(params.x));
|
|
335
335
|
if (params.y !== undefined) next.y = Math.round(Number(params.y));
|
|
336
|
-
|
|
336
|
+
setComponent(ctx.node, SimPosition, next);
|
|
337
337
|
}, { description: 'Move to position', params: 'x, y' });
|
|
338
338
|
|
|
339
339
|
/** @description Update agent properties (systemPrompt, radius, name, icon, description) */
|
|
340
340
|
register('sim.agent', 'action:update', async (ctx: ActionCtx, params: any) => {
|
|
341
341
|
if (params.systemPrompt !== undefined) {
|
|
342
342
|
const ai = getComponent(ctx.node, SimAi)!;
|
|
343
|
-
|
|
343
|
+
setComponent(ctx.node, SimAi, { ...ai, systemPrompt: String(params.systemPrompt) });
|
|
344
344
|
}
|
|
345
345
|
if (params.radius !== undefined) {
|
|
346
346
|
const pos = getComponent(ctx.node, SimPosition)!;
|
|
347
|
-
|
|
347
|
+
setComponent(ctx.node, SimPosition, { ...pos, radius: Number(params.radius) });
|
|
348
348
|
}
|
|
349
349
|
if (params.name !== undefined || params.icon !== undefined || params.description !== undefined) {
|
|
350
350
|
const desc = getComponent(ctx.node, SimDescriptive)!;
|
|
@@ -352,7 +352,7 @@ register('sim.agent', 'action:update', async (ctx: ActionCtx, params: any) => {
|
|
|
352
352
|
if (params.name !== undefined) next.name = String(params.name);
|
|
353
353
|
if (params.icon !== undefined) next.icon = String(params.icon);
|
|
354
354
|
if (params.description !== undefined) next.description = String(params.description);
|
|
355
|
-
|
|
355
|
+
setComponent(ctx.node, SimDescriptive, next);
|
|
356
356
|
}
|
|
357
357
|
}, { description: 'Update agent properties', params: 'systemPrompt?, radius?, name?, icon?, description?' });
|
|
358
358
|
|
|
@@ -376,17 +376,17 @@ register('sim.world', 'service', async (node, ctx) => {
|
|
|
376
376
|
const wp = node.$path;
|
|
377
377
|
|
|
378
378
|
async function getAllEntities() {
|
|
379
|
-
return (await ctx.
|
|
379
|
+
return (await ctx.tree.getChildren(wp)).items.filter(
|
|
380
380
|
(n) => n.$type === 'sim.agent' || n.$type === 'sim.item',
|
|
381
381
|
);
|
|
382
382
|
}
|
|
383
383
|
|
|
384
384
|
async function getAgents() {
|
|
385
|
-
return (await ctx.
|
|
385
|
+
return (await ctx.tree.getChildren(wp)).items.filter((n) => n.$type === 'sim.agent');
|
|
386
386
|
}
|
|
387
387
|
|
|
388
388
|
async function runRound() {
|
|
389
|
-
const world = await ctx.
|
|
389
|
+
const world = await ctx.tree.get(wp);
|
|
390
390
|
if (!world) return;
|
|
391
391
|
const cfg = getComponent(world, SimConfig);
|
|
392
392
|
if (!cfg?.running) return;
|
|
@@ -398,8 +398,8 @@ register('sim.world', 'service', async (node, ctx) => {
|
|
|
398
398
|
if (!agents.length) return;
|
|
399
399
|
|
|
400
400
|
// Phase: thinking
|
|
401
|
-
|
|
402
|
-
await ctx.
|
|
401
|
+
setComponent(world, SimRound, { ...round, phase: 'thinking' });
|
|
402
|
+
await ctx.tree.set(world);
|
|
403
403
|
|
|
404
404
|
const log = round.log ?? [];
|
|
405
405
|
const model = cfg.model ?? 'claude-haiku-4-5-20251001';
|
|
@@ -434,7 +434,7 @@ register('sim.world', 'service', async (node, ctx) => {
|
|
|
434
434
|
|
|
435
435
|
// Push event to directed target or all hearers
|
|
436
436
|
for (const hearer of nearAgents) {
|
|
437
|
-
const fresh = await ctx.
|
|
437
|
+
const fresh = await ctx.tree.get(hearer.$path);
|
|
438
438
|
if (!fresh) continue;
|
|
439
439
|
const hName = eName(fresh);
|
|
440
440
|
const eventType = to && to === hName ? 'speak' : 'hear';
|
|
@@ -446,7 +446,7 @@ register('sim.world', 'service', async (node, ctx) => {
|
|
|
446
446
|
data: { message: t.message as string },
|
|
447
447
|
ts,
|
|
448
448
|
});
|
|
449
|
-
await ctx.
|
|
449
|
+
await ctx.tree.set(fresh);
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
newEntries.push({
|
|
@@ -461,15 +461,15 @@ register('sim.world', 'service', async (node, ctx) => {
|
|
|
461
461
|
break;
|
|
462
462
|
}
|
|
463
463
|
case 'move': {
|
|
464
|
-
const fresh = await ctx.
|
|
464
|
+
const fresh = await ctx.tree.get(agent.$path);
|
|
465
465
|
if (!fresh) break;
|
|
466
466
|
const pos = getComponent(fresh, SimPosition)!;
|
|
467
|
-
|
|
467
|
+
setComponent(fresh, SimPosition, {
|
|
468
468
|
...pos,
|
|
469
469
|
x: Math.max(0, Math.min(cfg.width, t.x as number)),
|
|
470
470
|
y: Math.max(0, Math.min(cfg.height, t.y as number)),
|
|
471
471
|
});
|
|
472
|
-
await ctx.
|
|
472
|
+
await ctx.tree.set(fresh);
|
|
473
473
|
newEntries.push({
|
|
474
474
|
round: num,
|
|
475
475
|
agent: agentName,
|
|
@@ -481,13 +481,13 @@ register('sim.world', 'service', async (node, ctx) => {
|
|
|
481
481
|
break;
|
|
482
482
|
}
|
|
483
483
|
case 'remember': {
|
|
484
|
-
const fresh = await ctx.
|
|
484
|
+
const fresh = await ctx.tree.get(agent.$path);
|
|
485
485
|
if (!fresh) break;
|
|
486
486
|
const mem = getComponent(fresh, SimMemory);
|
|
487
|
-
|
|
487
|
+
setComponent(fresh, SimMemory, {
|
|
488
488
|
entries: [...(mem?.entries ?? []), t.text as string].slice(-20),
|
|
489
489
|
});
|
|
490
|
-
await ctx.
|
|
490
|
+
await ctx.tree.set(fresh);
|
|
491
491
|
break;
|
|
492
492
|
}
|
|
493
493
|
case 'interact': {
|
|
@@ -497,7 +497,7 @@ register('sim.world', 'service', async (node, ctx) => {
|
|
|
497
497
|
const targetPath = nameToPath.get(targetName);
|
|
498
498
|
if (!targetPath) break;
|
|
499
499
|
|
|
500
|
-
const targetNode = await ctx.
|
|
500
|
+
const targetNode = await ctx.tree.get(targetPath);
|
|
501
501
|
if (!targetNode) break;
|
|
502
502
|
|
|
503
503
|
// Check proximity
|
|
@@ -510,17 +510,17 @@ register('sim.world', 'service', async (node, ctx) => {
|
|
|
510
510
|
|
|
511
511
|
const actx: ActionCtx = {
|
|
512
512
|
node: targetNode,
|
|
513
|
-
|
|
513
|
+
tree: ctx.tree,
|
|
514
514
|
signal: AbortSignal.timeout(5000),
|
|
515
|
-
nc: serverNodeHandle(ctx.
|
|
515
|
+
nc: serverNodeHandle(ctx.tree),
|
|
516
516
|
};
|
|
517
517
|
const result = await (handler as any)(actx, data);
|
|
518
|
-
// Persist handler mutations (handlers don't call
|
|
519
|
-
await ctx.
|
|
518
|
+
// Persist handler mutations (handlers don't call tree.set — executeAction/caller does)
|
|
519
|
+
await ctx.tree.set(targetNode);
|
|
520
520
|
|
|
521
521
|
// Push event to target agent's inbox
|
|
522
522
|
if (targetNode.$type === 'sim.agent') {
|
|
523
|
-
const freshTarget = await ctx.
|
|
523
|
+
const freshTarget = await ctx.tree.get(targetPath);
|
|
524
524
|
if (freshTarget) {
|
|
525
525
|
pushAgentEvent(freshTarget, {
|
|
526
526
|
round: num,
|
|
@@ -529,7 +529,7 @@ register('sim.world', 'service', async (node, ctx) => {
|
|
|
529
529
|
data: { ...data, result },
|
|
530
530
|
ts,
|
|
531
531
|
});
|
|
532
|
-
await ctx.
|
|
532
|
+
await ctx.tree.set(freshTarget);
|
|
533
533
|
}
|
|
534
534
|
}
|
|
535
535
|
|
|
@@ -551,19 +551,19 @@ register('sim.world', 'service', async (node, ctx) => {
|
|
|
551
551
|
const freshEntities = await getAllEntities();
|
|
552
552
|
for (const a of freshEntities) {
|
|
553
553
|
const near = getNearby(a, freshEntities);
|
|
554
|
-
|
|
555
|
-
await ctx.
|
|
554
|
+
setComponent(a, SimNearby, { agents: near.map(eName) });
|
|
555
|
+
await ctx.tree.set(a);
|
|
556
556
|
}
|
|
557
557
|
|
|
558
558
|
// Advance round
|
|
559
|
-
const worldFresh = await ctx.
|
|
559
|
+
const worldFresh = await ctx.tree.get(wp);
|
|
560
560
|
if (!worldFresh) return;
|
|
561
|
-
|
|
561
|
+
setComponent(worldFresh, SimRound, {
|
|
562
562
|
current: num + 1,
|
|
563
563
|
phase: 'idle',
|
|
564
564
|
log: [...log, ...newEntries].slice(-50),
|
|
565
565
|
});
|
|
566
|
-
await ctx.
|
|
566
|
+
await ctx.tree.set(worldFresh);
|
|
567
567
|
console.log(`[sim] round ${num} done: ${newEntries.length} events`);
|
|
568
568
|
}
|
|
569
569
|
|
|
@@ -572,7 +572,7 @@ register('sim.world', 'service', async (node, ctx) => {
|
|
|
572
572
|
console.log(`[sim] started on ${wp}`);
|
|
573
573
|
while (!stopped) {
|
|
574
574
|
try {
|
|
575
|
-
const w = await ctx.
|
|
575
|
+
const w = await ctx.tree.get(wp);
|
|
576
576
|
const cfg = w ? getComponent(w, SimConfig) : null;
|
|
577
577
|
if (cfg?.running) await runRound();
|
|
578
578
|
await new Promise((r) => setTimeout(r, cfg?.roundDelay ?? 5000));
|
package/table/view.tsx
CHANGED
|
@@ -6,8 +6,13 @@ import { useSchema } from '@treenity/react/schema-loader';
|
|
|
6
6
|
import { Button } from '@treenity/react/ui/button';
|
|
7
7
|
import { Input } from '@treenity/react/ui/input';
|
|
8
8
|
import {
|
|
9
|
-
Pagination,
|
|
10
|
-
|
|
9
|
+
Pagination,
|
|
10
|
+
PaginationContent,
|
|
11
|
+
PaginationEllipsis,
|
|
12
|
+
PaginationItem,
|
|
13
|
+
PaginationLink,
|
|
14
|
+
PaginationNext,
|
|
15
|
+
PaginationPrevious,
|
|
11
16
|
} from '@treenity/react/ui/pagination';
|
|
12
17
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@treenity/react/ui/table';
|
|
13
18
|
import { useMemo } from 'react';
|
package/todo/types.ts
CHANGED
|
@@ -16,9 +16,9 @@ class TodoList {
|
|
|
16
16
|
/** @description Add a new todo item */
|
|
17
17
|
async add(data: { title: string }) {
|
|
18
18
|
if (!data.title?.trim()) throw new Error('Title required');
|
|
19
|
-
const { node,
|
|
19
|
+
const { node, tree } = getCtx();
|
|
20
20
|
const id = Date.now().toString(36);
|
|
21
|
-
await
|
|
21
|
+
await tree.set({
|
|
22
22
|
$path: `${node.$path}/${id}`,
|
|
23
23
|
$type: 'todo.item',
|
|
24
24
|
title: data.title.trim(),
|
package/todo/view.tsx
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type NodeData, register } from '@treenity/core';
|
|
2
2
|
import { useChildren, usePath } from '@treenity/react/hooks';
|
|
3
|
+
import { Button } from '@treenity/react/ui/button';
|
|
4
|
+
import { Input } from '@treenity/react/ui/input';
|
|
3
5
|
import { useState } from 'react';
|
|
4
6
|
import { TodoItem, TodoList } from './types';
|
|
5
7
|
|
|
@@ -19,17 +21,14 @@ function TodoListView({ value }: { value: NodeData }) {
|
|
|
19
21
|
<h2 className="text-xl font-bold mb-4">{list.title}</h2>
|
|
20
22
|
|
|
21
23
|
<div className="flex gap-2 mb-4">
|
|
22
|
-
<
|
|
23
|
-
className="flex-1
|
|
24
|
+
<Input
|
|
25
|
+
className="flex-1"
|
|
24
26
|
placeholder="What needs to be done?"
|
|
25
27
|
value={draft}
|
|
26
28
|
onChange={e => setDraft(e.target.value)}
|
|
27
29
|
onKeyDown={e => e.key === 'Enter' && handleAdd()}
|
|
28
30
|
/>
|
|
29
|
-
<
|
|
30
|
-
className="bg-blue-600 text-white px-3 py-1.5 rounded text-sm"
|
|
31
|
-
onClick={handleAdd}
|
|
32
|
-
>Add</button>
|
|
31
|
+
<Button size="sm" onClick={handleAdd}>Add</Button>
|
|
33
32
|
</div>
|
|
34
33
|
|
|
35
34
|
<ul className="space-y-1">
|
|
@@ -47,16 +46,16 @@ function TodoItemRow({ value }: { value: NodeData }) {
|
|
|
47
46
|
return (
|
|
48
47
|
<li
|
|
49
48
|
className="flex items-center gap-2 px-3 py-2 rounded
|
|
50
|
-
hover:bg-
|
|
49
|
+
hover:bg-muted cursor-pointer"
|
|
51
50
|
onClick={() => item.toggle()}
|
|
52
51
|
>
|
|
53
52
|
<span className={`w-4 h-4 rounded border flex items-center
|
|
54
53
|
justify-center text-xs ${item.done
|
|
55
|
-
? 'bg-
|
|
56
|
-
: 'border-
|
|
54
|
+
? 'bg-primary border-primary text-primary-foreground'
|
|
55
|
+
: 'border-input'}`}>
|
|
57
56
|
{item.done ? '✓' : ''}
|
|
58
57
|
</span>
|
|
59
|
-
<span className={item.done ? 'line-through text-
|
|
58
|
+
<span className={item.done ? 'line-through text-muted-foreground' : ''}>
|
|
60
59
|
{item.title}
|
|
61
60
|
</span>
|
|
62
61
|
</li>
|
package/whisper/inbox.ts
CHANGED
|
@@ -19,7 +19,7 @@ register('whisper.inbox', 'service', async (node, ctx) => {
|
|
|
19
19
|
const sent = new Set<string>();
|
|
20
20
|
|
|
21
21
|
// Mark existing COMPLETED transcriptions as already processed
|
|
22
|
-
const { items } = await ctx.
|
|
22
|
+
const { items } = await ctx.tree.getChildren(config.source);
|
|
23
23
|
for (const child of items) {
|
|
24
24
|
if (child.$type !== 'whisper.transcription') continue;
|
|
25
25
|
if (getText(child)) sent.add(child.$path);
|
|
@@ -29,7 +29,7 @@ register('whisper.inbox', 'service', async (node, ctx) => {
|
|
|
29
29
|
const unsub = ctx.subscribe(config.source, (event) => {
|
|
30
30
|
if (event.type !== 'set' && event.type !== 'patch') return;
|
|
31
31
|
|
|
32
|
-
ctx.
|
|
32
|
+
ctx.tree.get(event.path).then(async (n) => {
|
|
33
33
|
if (!n || n.$type !== 'whisper.transcription') return;
|
|
34
34
|
if (sent.has(n.$path)) return;
|
|
35
35
|
|
|
@@ -40,7 +40,7 @@ register('whisper.inbox', 'service', async (node, ctx) => {
|
|
|
40
40
|
|
|
41
41
|
const taskId = `t-${Date.now()}`;
|
|
42
42
|
const taskPath = `${config.target}/tasks/${taskId}`;
|
|
43
|
-
await ctx.
|
|
43
|
+
await ctx.tree.set(createNode(taskPath, 'agent.task', {
|
|
44
44
|
prompt: text,
|
|
45
45
|
status: 'pending',
|
|
46
46
|
createdAt: Date.now(),
|