@hanzo/rooms 0.1.0
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/LICENSE-APACHE +205 -0
- package/LICENSE-MIT +21 -0
- package/README.md +23 -0
- package/dist/Account.d.ts +8 -0
- package/dist/Account.js +58 -0
- package/dist/Beluga.d.ts +12 -0
- package/dist/Beluga.js +152 -0
- package/dist/Billing.d.ts +1 -0
- package/dist/Billing.js +31 -0
- package/dist/Board.d.ts +9 -0
- package/dist/Board.js +447 -0
- package/dist/Boxes.d.ts +1 -0
- package/dist/Boxes.js +173 -0
- package/dist/CalScheduler.d.ts +15 -0
- package/dist/CalScheduler.js +196 -0
- package/dist/Chat.d.ts +35 -0
- package/dist/Chat.js +1049 -0
- package/dist/Column.d.ts +15 -0
- package/dist/Column.js +88 -0
- package/dist/Contacts.d.ts +1 -0
- package/dist/Contacts.js +301 -0
- package/dist/Directory.d.ts +24 -0
- package/dist/Directory.js +356 -0
- package/dist/Drive.d.ts +2 -0
- package/dist/Drive.js +466 -0
- package/dist/Find.d.ts +7 -0
- package/dist/Find.js +82 -0
- package/dist/Guide.d.ts +6 -0
- package/dist/Guide.js +272 -0
- package/dist/Home.d.ts +39 -0
- package/dist/Home.js +306 -0
- package/dist/Inbox.d.ts +9 -0
- package/dist/Inbox.js +193 -0
- package/dist/Market.d.ts +3 -0
- package/dist/Market.js +149 -0
- package/dist/Me.d.ts +27 -0
- package/dist/Me.js +251 -0
- package/dist/Meet.d.ts +9 -0
- package/dist/Meet.js +370 -0
- package/dist/ModeMark.d.ts +8 -0
- package/dist/ModeMark.js +18 -0
- package/dist/OrgSwitcher.d.ts +31 -0
- package/dist/OrgSwitcher.js +120 -0
- package/dist/Orgs.d.ts +10 -0
- package/dist/Orgs.js +582 -0
- package/dist/Profile.d.ts +4 -0
- package/dist/Profile.js +67 -0
- package/dist/Prose.d.ts +5 -0
- package/dist/Prose.js +111 -0
- package/dist/ProviderMark.d.ts +17 -0
- package/dist/ProviderMark.js +277 -0
- package/dist/Recents.d.ts +10 -0
- package/dist/Recents.js +91 -0
- package/dist/RightPane.d.ts +13 -0
- package/dist/RightPane.js +426 -0
- package/dist/Room.d.ts +8 -0
- package/dist/Room.js +50 -0
- package/dist/Scene.d.ts +12 -0
- package/dist/Scene.js +105 -0
- package/dist/Settings.d.ts +46 -0
- package/dist/Settings.js +321 -0
- package/dist/Setup.d.ts +1 -0
- package/dist/Setup.js +36 -0
- package/dist/Share.d.ts +3 -0
- package/dist/Share.js +130 -0
- package/dist/Shared.d.ts +1 -0
- package/dist/Shared.js +113 -0
- package/dist/Shell.d.ts +63 -0
- package/dist/Shell.js +1225 -0
- package/dist/Start.d.ts +1 -0
- package/dist/Start.js +96 -0
- package/dist/Support.d.ts +14 -0
- package/dist/Support.js +122 -0
- package/dist/Talk.d.ts +4 -0
- package/dist/Talk.js +107 -0
- package/dist/Upgrade.d.ts +11 -0
- package/dist/Upgrade.js +75 -0
- package/dist/Usage.d.ts +1 -0
- package/dist/Usage.js +21 -0
- package/dist/Vibe.d.ts +2 -0
- package/dist/Vibe.js +188 -0
- package/dist/Work.d.ts +12 -0
- package/dist/Work.js +256 -0
- package/dist/bots/Bots.d.ts +2 -0
- package/dist/bots/Bots.js +456 -0
- package/dist/bots/Lights.d.ts +8 -0
- package/dist/bots/Lights.js +13 -0
- package/dist/bots/api.d.ts +120 -0
- package/dist/bots/api.js +75 -0
- package/dist/bots/face.d.ts +5 -0
- package/dist/bots/face.js +13 -0
- package/dist/bots/ink.d.ts +7 -0
- package/dist/bots/ink.js +29 -0
- package/dist/bots/kit.d.ts +113 -0
- package/dist/bots/kit.js +43 -0
- package/dist/bots/runtime.d.ts +4 -0
- package/dist/bots/runtime.js +326 -0
- package/dist/card.d.ts +52 -0
- package/dist/card.js +52 -0
- package/dist/cast.d.ts +143 -0
- package/dist/cast.js +251 -0
- package/dist/connect.d.ts +29 -0
- package/dist/connect.js +64 -0
- package/dist/contacts-store.d.ts +111 -0
- package/dist/contacts-store.js +276 -0
- package/dist/conversations.d.ts +54 -0
- package/dist/conversations.js +98 -0
- package/dist/copy.d.ts +23 -0
- package/dist/copy.js +39 -0
- package/dist/crew.d.ts +36 -0
- package/dist/crew.js +304 -0
- package/dist/dev.d.ts +3 -0
- package/dist/dev.js +102 -0
- package/dist/enso.d.ts +70 -0
- package/dist/enso.js +112 -0
- package/dist/failure.d.ts +57 -0
- package/dist/failure.js +143 -0
- package/dist/ground.d.ts +32 -0
- package/dist/ground.js +32 -0
- package/dist/host.d.ts +76 -0
- package/dist/host.js +76 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +12 -0
- package/dist/kept.d.ts +5 -0
- package/dist/kept.js +27 -0
- package/dist/lib/account.d.ts +49 -0
- package/dist/lib/account.js +229 -0
- package/dist/lib/ai.d.ts +46 -0
- package/dist/lib/ai.js +140 -0
- package/dist/lib/api.d.ts +43 -0
- package/dist/lib/api.js +62 -0
- package/dist/lib/bare.d.ts +9 -0
- package/dist/lib/bare.js +7 -0
- package/dist/lib/coding.d.ts +105 -0
- package/dist/lib/coding.js +87 -0
- package/dist/lib/destination.d.ts +102 -0
- package/dist/lib/destination.js +260 -0
- package/dist/lib/durable.d.ts +66 -0
- package/dist/lib/durable.js +107 -0
- package/dist/lib/first.d.ts +11 -0
- package/dist/lib/first.js +23 -0
- package/dist/lib/home.d.ts +61 -0
- package/dist/lib/home.js +54 -0
- package/dist/lib/host.d.ts +104 -0
- package/dist/lib/host.js +132 -0
- package/dist/lib/hostname.d.ts +1 -0
- package/dist/lib/hostname.js +14 -0
- package/dist/lib/hydrated.d.ts +1 -0
- package/dist/lib/hydrated.js +20 -0
- package/dist/lib/integrations.d.ts +21 -0
- package/dist/lib/integrations.js +1 -0
- package/dist/lib/intent.d.ts +6 -0
- package/dist/lib/intent.js +71 -0
- package/dist/lib/invite.d.ts +155 -0
- package/dist/lib/invite.js +245 -0
- package/dist/lib/limits.d.ts +55 -0
- package/dist/lib/limits.js +119 -0
- package/dist/lib/list.d.ts +9 -0
- package/dist/lib/list.js +11 -0
- package/dist/lib/mark.d.ts +13 -0
- package/dist/lib/mark.js +85 -0
- package/dist/lib/mix.d.ts +22 -0
- package/dist/lib/mix.js +22 -0
- package/dist/lib/models.d.ts +27 -0
- package/dist/lib/models.js +106 -0
- package/dist/lib/org.d.ts +109 -0
- package/dist/lib/org.js +140 -0
- package/dist/lib/path.d.ts +9 -0
- package/dist/lib/path.js +29 -0
- package/dist/lib/plans.d.ts +193 -0
- package/dist/lib/plans.js +310 -0
- package/dist/lib/prose.d.ts +13 -0
- package/dist/lib/prose.js +62 -0
- package/dist/lib/reach.d.ts +17 -0
- package/dist/lib/reach.js +70 -0
- package/dist/lib/rooms.json +23 -0
- package/dist/lib/served.d.ts +20 -0
- package/dist/lib/served.js +92 -0
- package/dist/lib/session.d.ts +211 -0
- package/dist/lib/session.js +493 -0
- package/dist/lib/share.d.ts +131 -0
- package/dist/lib/share.js +216 -0
- package/dist/lib/signout.d.ts +14 -0
- package/dist/lib/signout.js +40 -0
- package/dist/lib/tags.d.ts +27 -0
- package/dist/lib/tags.js +46 -0
- package/dist/lib/tap.d.ts +17 -0
- package/dist/lib/tap.js +9 -0
- package/dist/lib/tier.d.ts +115 -0
- package/dist/lib/tier.js +207 -0
- package/dist/lib/tiers.d.ts +9 -0
- package/dist/lib/tiers.js +141 -0
- package/dist/lib/todo.d.ts +125 -0
- package/dist/lib/todo.js +379 -0
- package/dist/look.d.ts +1 -0
- package/dist/look.js +21 -0
- package/dist/meters.d.ts +8 -0
- package/dist/meters.js +31 -0
- package/dist/model.d.ts +4 -0
- package/dist/model.js +44 -0
- package/dist/network.d.ts +64 -0
- package/dist/network.js +216 -0
- package/dist/open.d.ts +81 -0
- package/dist/open.js +149 -0
- package/dist/pane.d.ts +92 -0
- package/dist/pane.js +231 -0
- package/dist/progress.d.ts +46 -0
- package/dist/progress.js +51 -0
- package/dist/recent.d.ts +67 -0
- package/dist/recent.js +161 -0
- package/dist/rooms.d.ts +131 -0
- package/dist/rooms.js +195 -0
- package/dist/roster.d.ts +28 -0
- package/dist/roster.js +70 -0
- package/dist/span.d.ts +14 -0
- package/dist/span.js +50 -0
- package/dist/speech.d.ts +44 -0
- package/dist/speech.js +229 -0
- package/dist/stars.d.ts +16 -0
- package/dist/stars.js +75 -0
- package/dist/tabs.d.ts +26 -0
- package/dist/tabs.js +25 -0
- package/dist/team.d.ts +11 -0
- package/dist/team.gen.d.ts +10 -0
- package/dist/team.gen.js +11 -0
- package/dist/team.js +30 -0
- package/dist/tools.d.ts +35 -0
- package/dist/tools.js +143 -0
- package/dist/tours.d.ts +24 -0
- package/dist/tours.js +311 -0
- package/dist/waiting.d.ts +15 -0
- package/dist/waiting.js +17 -0
- package/dist/where.d.ts +54 -0
- package/dist/where.js +14 -0
- package/package.json +108 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// The Bots Room.
|
|
4
|
+
//
|
|
5
|
+
// Bots are autonomous workers for your organization: each has a model,
|
|
6
|
+
// instructions, tools, and a record of everything it has accomplished.
|
|
7
|
+
//
|
|
8
|
+
// In live mode, this connects directly to your organization's bots and sessions.
|
|
9
|
+
// When visiting without an account, an interactive Example Workspace lets you
|
|
10
|
+
// explore how bots use computers to automate sales, triage inboxes, and review code.
|
|
11
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
12
|
+
import { Monitor, Plus, Search, Send, Trash2 } from 'lucide-react';
|
|
13
|
+
import { useIam } from '@hanzo/iam/react';
|
|
14
|
+
import { useAi, ENSO } from '../lib/ai.js';
|
|
15
|
+
import { say } from '../failure.js';
|
|
16
|
+
import { Input, Text, XStack, YStack } from '@hanzo/gui';
|
|
17
|
+
import { Button } from '@hanzo/ui';
|
|
18
|
+
import { Lights } from './Lights.js';
|
|
19
|
+
import { Face } from './face.js';
|
|
20
|
+
import { Runtime } from './runtime.js';
|
|
21
|
+
import { DIM, FAINT, GROUND, INK, LINE, RAISED, SURFACE } from './ink.js';
|
|
22
|
+
import { BARE, BOXED, CARD, CLIP, GHOST, LABEL, NOTE, TAB } from './kit.js';
|
|
23
|
+
import { ALWAYS, ONCE, launch, sessions, steer } from './api.js';
|
|
24
|
+
/** How long ago, in the words a roster row has space for. */
|
|
25
|
+
function when(iso) {
|
|
26
|
+
if (!iso)
|
|
27
|
+
return '';
|
|
28
|
+
const t = new Date(iso);
|
|
29
|
+
if (Number.isNaN(t.getTime()))
|
|
30
|
+
return '';
|
|
31
|
+
const days = Math.floor((Date.now() - t.getTime()) / 86_400_000);
|
|
32
|
+
if (days === 0)
|
|
33
|
+
return t.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
34
|
+
if (days === 1)
|
|
35
|
+
return 'Yesterday';
|
|
36
|
+
if (days < 7)
|
|
37
|
+
return t.toLocaleDateString([], { weekday: 'short' });
|
|
38
|
+
return t.toLocaleDateString([], { month: 'short', day: 'numeric' });
|
|
39
|
+
}
|
|
40
|
+
/** What a run cost, from the fields the run actually recorded. */
|
|
41
|
+
function cost(r) {
|
|
42
|
+
const tokens = (r.promptTokens ?? 0) + (r.completionTokens ?? 0);
|
|
43
|
+
return [r.model, r.durationMs ? `${(r.durationMs / 1000).toFixed(1)}s` : '', tokens ? `${tokens} tokens` : '']
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.join(' · ');
|
|
46
|
+
}
|
|
47
|
+
/** When something happened, as a number to sort by. */
|
|
48
|
+
const at = (iso) => {
|
|
49
|
+
const t = iso ? new Date(iso).getTime() : NaN;
|
|
50
|
+
return Number.isNaN(t) ? 0 : t;
|
|
51
|
+
};
|
|
52
|
+
/** A session the platform still considers open. Only these can be steered:
|
|
53
|
+
* the route takes a command for a finished session and records it against a
|
|
54
|
+
* log nothing is reading, so a control on one is a control over nothing. */
|
|
55
|
+
const open = (s) => s.status === 'running' || s.status === 'paused';
|
|
56
|
+
/** A Bot's recorded runs as the turns they were. */
|
|
57
|
+
function turns(detail) {
|
|
58
|
+
return (detail?.recentRuns ?? []).flatMap((r) => {
|
|
59
|
+
const when = at(r.createdAt);
|
|
60
|
+
const out = [];
|
|
61
|
+
if (r.input)
|
|
62
|
+
out.push({ id: `${r.id}-in`, at: when, who: 'you', text: r.input });
|
|
63
|
+
out.push({
|
|
64
|
+
id: `${r.id}-out`,
|
|
65
|
+
at: when + 1,
|
|
66
|
+
who: 'bot',
|
|
67
|
+
text: r.error || r.output || 'This run recorded no output.',
|
|
68
|
+
note: cost(r),
|
|
69
|
+
failed: Boolean(r.error),
|
|
70
|
+
});
|
|
71
|
+
return out;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const EXAMPLE_BOTS = [
|
|
75
|
+
{
|
|
76
|
+
id: 'bot-sales-outbound',
|
|
77
|
+
avatar: '/bots/sales-outbound.png',
|
|
78
|
+
name: 'Sales Outbound',
|
|
79
|
+
description: 'Prospecting, lead qualification, and cold outreach',
|
|
80
|
+
model: 'claude-3-5-sonnet',
|
|
81
|
+
executionMode: ALWAYS,
|
|
82
|
+
schedule: '0 9 * * 1-5',
|
|
83
|
+
updatedAt: new Date(Date.now() - 1000 * 60 * 12).toISOString(),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'bot-chief',
|
|
87
|
+
avatar: '/bots/chief.png',
|
|
88
|
+
name: 'Chief',
|
|
89
|
+
description: 'Executive briefing, team updates, and orchestration',
|
|
90
|
+
model: 'claude-3-5-sonnet',
|
|
91
|
+
executionMode: ALWAYS,
|
|
92
|
+
schedule: '0 8 * * *',
|
|
93
|
+
updatedAt: new Date(Date.now() - 1000 * 60 * 45).toISOString(),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'bot-inbox-manager',
|
|
97
|
+
avatar: '/bots/inbox-manager.png',
|
|
98
|
+
name: 'Inbox Manager',
|
|
99
|
+
description: 'Triage email, draft responses, and surface urgent items',
|
|
100
|
+
model: 'claude-3-5-sonnet',
|
|
101
|
+
executionMode: ALWAYS,
|
|
102
|
+
schedule: '*/15 * * * *',
|
|
103
|
+
updatedAt: new Date(Date.now() - 1000 * 60 * 90).toISOString(),
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'bot-code-reviewer',
|
|
107
|
+
name: 'Code Reviewer',
|
|
108
|
+
description: 'Analyze pull requests, check test suites, and style',
|
|
109
|
+
model: 'claude-3-5-sonnet',
|
|
110
|
+
executionMode: ONCE,
|
|
111
|
+
updatedAt: new Date(Date.now() - 1000 * 60 * 180).toISOString(),
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'bot-research-analyst',
|
|
115
|
+
name: 'Research Analyst',
|
|
116
|
+
description: 'Track industry trends, competitive moves, and filings',
|
|
117
|
+
model: 'claude-3-5-sonnet',
|
|
118
|
+
executionMode: ONCE,
|
|
119
|
+
updatedAt: new Date(Date.now() - 1000 * 60 * 300).toISOString(),
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
const EXAMPLE_ACTIONS = {
|
|
123
|
+
'Sales Outbound': {
|
|
124
|
+
title: 'Computer',
|
|
125
|
+
status: 'Done',
|
|
126
|
+
summary: 'Reviewed 52 new outbound leads across target accounts. Qualified 18 high-fit profiles and prepared tailored email drafts.',
|
|
127
|
+
tasks: [
|
|
128
|
+
'Verified domain deliverability and prospect contact records',
|
|
129
|
+
'Extracted buyer intent signals from company milestones',
|
|
130
|
+
'Queued 18 personalized emails for Armand’s review',
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
'Chief': {
|
|
134
|
+
title: 'Computer',
|
|
135
|
+
status: 'Done',
|
|
136
|
+
summary: 'Compiled morning briefing, synced calendar events, and highlighted team priorities.',
|
|
137
|
+
tasks: [
|
|
138
|
+
'Aggregated GitHub pull requests and release milestones',
|
|
139
|
+
'Organized 3 priority decisions for today',
|
|
140
|
+
'Synchronized executive calendar and shared prep notes',
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
'Inbox Manager': {
|
|
144
|
+
title: 'Computer',
|
|
145
|
+
status: 'Done',
|
|
146
|
+
summary: 'Triaged 34 unread messages, flagged 4 urgent requests, and drafted replies for approval.',
|
|
147
|
+
tasks: [
|
|
148
|
+
'Categorized incoming inquiries by priority and topic',
|
|
149
|
+
'Drafted context-aware responses citing knowledge base docs',
|
|
150
|
+
'Scheduled followup reminders for unresolved partner threads',
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
'Code Reviewer': {
|
|
154
|
+
title: 'Computer',
|
|
155
|
+
status: 'Done',
|
|
156
|
+
summary: 'Analyzed pull request #142 for architectural consistency, security considerations, and edge cases.',
|
|
157
|
+
tasks: [
|
|
158
|
+
'Verified zero memory leaks and confirmed strict typing',
|
|
159
|
+
'Validated test suite passes across all target environments',
|
|
160
|
+
'Posted inline review suggestions for error handling',
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
'Research Analyst': {
|
|
164
|
+
title: 'Computer',
|
|
165
|
+
status: 'Done',
|
|
166
|
+
summary: 'Indexed frontier model benchmarks, competitive releases, and performance telemetry.',
|
|
167
|
+
tasks: [
|
|
168
|
+
'Indexed latest release notes and technical benchmarks',
|
|
169
|
+
'Created comparative capability matrix',
|
|
170
|
+
'Generated summary digest for the engineering team',
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
export function Bots() {
|
|
175
|
+
const { client, ready } = useAi();
|
|
176
|
+
const { user, isAuthenticated } = useIam();
|
|
177
|
+
// A client EXISTS signed out — `useAi` hands back an anonymous one so the
|
|
178
|
+
// free chat lane works — so holding one is not proof of a reader. Every read
|
|
179
|
+
// below is org-scoped and answers 403 without a session, so this is the gate.
|
|
180
|
+
const live = Boolean(client && isAuthenticated && ready);
|
|
181
|
+
const [roster, setRoster] = useState([]);
|
|
182
|
+
const [rosterFailed, setRosterFailed] = useState(null);
|
|
183
|
+
const [loading, setLoading] = useState(false);
|
|
184
|
+
const [query, setQuery] = useState('');
|
|
185
|
+
const [picked, setPicked] = useState('');
|
|
186
|
+
const [detail, setDetail] = useState(null);
|
|
187
|
+
const [own, setOwn] = useState([]);
|
|
188
|
+
const [draft, setDraft] = useState('');
|
|
189
|
+
const [trialTurns, setTrialTurns] = useState([]);
|
|
190
|
+
const [running, setRunning] = useState(false);
|
|
191
|
+
const [failed, setFailed] = useState(null);
|
|
192
|
+
const [pane, setPane] = useState(false);
|
|
193
|
+
const [making, setMaking] = useState(false);
|
|
194
|
+
const [cron, setCron] = useState('');
|
|
195
|
+
const read = useCallback(async () => {
|
|
196
|
+
if (!client)
|
|
197
|
+
return;
|
|
198
|
+
setLoading(true);
|
|
199
|
+
try {
|
|
200
|
+
setRoster(await client.agents.list());
|
|
201
|
+
setRosterFailed(null);
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
setRosterFailed(say(e, 'this org’s bots'));
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
setLoading(false);
|
|
208
|
+
}
|
|
209
|
+
}, [client]);
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
if (live)
|
|
212
|
+
void read();
|
|
213
|
+
}, [live, read]);
|
|
214
|
+
// In live mode, use platform roster. When exploring as a visitor, show example workspace bots.
|
|
215
|
+
const activeRoster = useMemo(() => {
|
|
216
|
+
if (live && roster.length > 0)
|
|
217
|
+
return roster;
|
|
218
|
+
if (!live)
|
|
219
|
+
return EXAMPLE_BOTS;
|
|
220
|
+
return roster;
|
|
221
|
+
}, [live, roster]);
|
|
222
|
+
const shown = useMemo(() => {
|
|
223
|
+
const q = query.trim().toLowerCase();
|
|
224
|
+
if (!q)
|
|
225
|
+
return activeRoster;
|
|
226
|
+
return activeRoster.filter((b) => b.name.toLowerCase().includes(q) || (b.description ?? '').toLowerCase().includes(q));
|
|
227
|
+
}, [activeRoster, query]);
|
|
228
|
+
const bot = useMemo(() => activeRoster.find((b) => b.name === picked) ?? shown[0] ?? activeRoster[0] ?? null, [activeRoster, shown, picked]);
|
|
229
|
+
// Its record and its work, re-read after anything this room does to it —
|
|
230
|
+
// the platform's answer is the only account of what happened.
|
|
231
|
+
const refresh = useCallback(async () => {
|
|
232
|
+
if (!client || !bot)
|
|
233
|
+
return;
|
|
234
|
+
const [d, s] = await Promise.allSettled([client.agents.get(bot.name), sessions(client, bot.name)]);
|
|
235
|
+
if (d.status === 'fulfilled')
|
|
236
|
+
setDetail(d.value);
|
|
237
|
+
setOwn(s.status === 'fulfilled' ? s.value : []);
|
|
238
|
+
}, [client, bot]);
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
if (!client || !bot) {
|
|
241
|
+
setDetail(null);
|
|
242
|
+
setOwn([]);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const stop = new AbortController();
|
|
246
|
+
setCron(bot.schedule ?? '');
|
|
247
|
+
client.agents
|
|
248
|
+
.get(bot.name, { signal: stop.signal })
|
|
249
|
+
.then(setDetail)
|
|
250
|
+
.catch(() => {
|
|
251
|
+
if (!stop.signal.aborted)
|
|
252
|
+
setDetail(null);
|
|
253
|
+
});
|
|
254
|
+
// No limit of its own. The route has no per-agent filter, so the page it
|
|
255
|
+
// answers is the ORG's and the narrowing happens after — ask for twenty
|
|
256
|
+
// and a bot whose work is older than the org's twenty newest sessions
|
|
257
|
+
// reads as having none.
|
|
258
|
+
sessions(client, bot.name, undefined, stop.signal)
|
|
259
|
+
.then(setOwn)
|
|
260
|
+
.catch(() => setOwn([]));
|
|
261
|
+
return () => stop.abort();
|
|
262
|
+
}, [client, bot]);
|
|
263
|
+
// ONE STREAM, IN THE ORDER IT HAPPENED. A run and a session are two records
|
|
264
|
+
// of the same Bot working, so they are read together and sorted by the clock
|
|
265
|
+
// the platform put on them — newest at the bottom, where a reader who has
|
|
266
|
+
// just asked for something is looking.
|
|
267
|
+
const computerAction = bot ? EXAMPLE_ACTIONS[bot.name] ?? null : null;
|
|
268
|
+
const stream = useMemo(() => {
|
|
269
|
+
if (!live) {
|
|
270
|
+
return trialTurns.map((t) => ({ at: t.at, key: t.id, turn: t, session: null }));
|
|
271
|
+
}
|
|
272
|
+
return [
|
|
273
|
+
...turns(detail).map((t) => ({ at: t.at, key: t.id, turn: t, session: null })),
|
|
274
|
+
...own.map((s) => ({ at: at(s.updatedAt || s.startedAt || s.createdAt), key: s.id, turn: null, session: s })),
|
|
275
|
+
].sort((a, b) => a.at - b.at);
|
|
276
|
+
}, [live, detail, own, trialTurns]);
|
|
277
|
+
const always = bot?.executionMode === ALWAYS;
|
|
278
|
+
// Newest last means the newest is off-screen on a Bot with any history, so
|
|
279
|
+
// the stream opens where a reader is looking rather than at its oldest run.
|
|
280
|
+
const foot = useRef(null);
|
|
281
|
+
useEffect(() => {
|
|
282
|
+
foot.current?.scrollIntoView({ block: 'end' });
|
|
283
|
+
}, [stream]);
|
|
284
|
+
/** One task, run now. */
|
|
285
|
+
const run = useCallback(async (e) => {
|
|
286
|
+
if (e)
|
|
287
|
+
e.preventDefault();
|
|
288
|
+
const input = draft.trim();
|
|
289
|
+
if (!bot || !input || running)
|
|
290
|
+
return;
|
|
291
|
+
if (!live) {
|
|
292
|
+
setDraft('');
|
|
293
|
+
const userTurn = { id: `trial-${Date.now()}-in`, at: Date.now(), who: 'you', text: input };
|
|
294
|
+
setTrialTurns((prev) => [...prev, userTurn]);
|
|
295
|
+
setTimeout(() => {
|
|
296
|
+
const botTurn = {
|
|
297
|
+
id: `trial-${Date.now()}-out`,
|
|
298
|
+
at: Date.now(),
|
|
299
|
+
who: 'bot',
|
|
300
|
+
text: `Received: "${input}". Running on ${bot.model} with computer tools enabled. Sign in to your organization to deploy bots into production.`,
|
|
301
|
+
note: `${bot.model} · 0.8s · 142 tokens`,
|
|
302
|
+
};
|
|
303
|
+
setTrialTurns((prev) => [...prev, botTurn]);
|
|
304
|
+
}, 400);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (!client)
|
|
308
|
+
return;
|
|
309
|
+
setRunning(true);
|
|
310
|
+
setFailed(null);
|
|
311
|
+
try {
|
|
312
|
+
await client.agents.run(bot.name, input);
|
|
313
|
+
setDraft('');
|
|
314
|
+
await refresh();
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
setFailed(err instanceof Error ? err.message : String(err));
|
|
318
|
+
}
|
|
319
|
+
finally {
|
|
320
|
+
setRunning(false);
|
|
321
|
+
}
|
|
322
|
+
}, [live, client, bot, draft, running, refresh]);
|
|
323
|
+
/** Stays up, or runs when asked. The platform REFUSES long-running without a
|
|
324
|
+
* cron — measured, 400 naming the field — so the cron is asked for beside
|
|
325
|
+
* the switch rather than guessed at here. */
|
|
326
|
+
const keepUp = useCallback(async () => {
|
|
327
|
+
if (!client || !bot)
|
|
328
|
+
return;
|
|
329
|
+
setFailed(null);
|
|
330
|
+
try {
|
|
331
|
+
const saved = always
|
|
332
|
+
? await client.agents.update(bot.name, { executionMode: ONCE })
|
|
333
|
+
: await client.agents.update(bot.name, { executionMode: ALWAYS, schedule: cron.trim() });
|
|
334
|
+
setRoster((r) => r.map((b) => (b.name === saved.name ? saved : b)));
|
|
335
|
+
}
|
|
336
|
+
catch (e) {
|
|
337
|
+
setFailed(e instanceof Error ? e.message : String(e));
|
|
338
|
+
}
|
|
339
|
+
}, [client, bot, always, cron]);
|
|
340
|
+
const retire = useCallback(async () => {
|
|
341
|
+
if (!client || !bot)
|
|
342
|
+
return;
|
|
343
|
+
setFailed(null);
|
|
344
|
+
try {
|
|
345
|
+
await client.agents.delete(bot.name);
|
|
346
|
+
setPicked('');
|
|
347
|
+
await read();
|
|
348
|
+
}
|
|
349
|
+
catch (e) {
|
|
350
|
+
setFailed(say(e, 'this bot', 'save'));
|
|
351
|
+
}
|
|
352
|
+
}, [client, bot, read]);
|
|
353
|
+
return (_jsxs(XStack, { width: "100%", height: "100%", overflow: "hidden", bg: GROUND, "$max-lg": { overflowX: 'auto', overflowY: 'hidden' }, "$platform-web": { scrollSnapType: 'x mandatory' }, children: [_jsxs(YStack, { width: 280, minW: 280, height: "100%", bg: GROUND, borderRightWidth: 1, borderColor: LINE, "$max-lg": { width: '85%', maxW: 320 }, "$platform-web": { scrollSnapAlign: 'start' }, children: [_jsxs(XStack, { pt: 16, px: 18, pb: 12, items: "center", justify: "space-between", children: [_jsx(Lights, {}), _jsx(XStack, { ...GHOST, title: live ? 'New bot' : 'Sign in to keep a bot', onClick: () => setMaking((m) => !m), "aria-label": "New bot", "aria-expanded": making, disabled: !live, opacity: live ? 1 : 0.4, children: _jsx(Plus, { size: 18, color: DIM }) })] }), making && live ? (_jsx(Hire, { onDone: async (made) => {
|
|
354
|
+
setMaking(false);
|
|
355
|
+
setPicked(made.name);
|
|
356
|
+
await read();
|
|
357
|
+
}, onCancel: () => setMaking(false) })) : null, _jsx(YStack, { px: 14, pb: 12, children: _jsxs(XStack, { items: "center", gap: 8, bg: SURFACE, borderWidth: 1, borderColor: LINE, rounded: 8, px: 12, py: 8, children: [_jsx(Search, { size: 14, color: DIM, "aria-hidden": true }), _jsx(Input, { ...BARE, value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search", "aria-label": "Search bots", fontSize: "$2", flex: 1, minW: 0 })] }) }), _jsxs(YStack, { "data-hz": "roster", flex: 1, overflowY: "auto", px: 10, gap: 2, children: [live && rosterFailed ? (_jsx(Text, { ...NOTE, children: rosterFailed })) : live && loading && roster.length === 0 ? (_jsx(Text, { ...NOTE, children: "Reading the roster\u2026" })) : live && shown.length === 0 ? (_jsx(Text, { ...NOTE, children: roster.length === 0 ? 'This org keeps no bots yet.' : 'No bot matches that.' })) : null, shown.map((b) => {
|
|
358
|
+
const on = bot?.name === b.name;
|
|
359
|
+
return (_jsxs(XStack, { render: "button", type: 'button', "data-hz": "bot", "data-name": b.name, "aria-current": on, onClick: () => setPicked(b.name), items: "center", gap: 12, px: 12, py: 10, rounded: 10, bg: on ? SURFACE : 'transparent', borderWidth: 0, cursor: "pointer", width: "100%", children: [_jsx(Face, { bot: b, size: 36 }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: 8, children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: on ? INK : DIM, children: b.name }), _jsx(Text, { fontSize: "$1", color: FAINT, whiteSpace: "nowrap", children: when(b.updatedAt) })] }), _jsx(Text, { ...CLIP, fontSize: "$2", color: DIM, text: "left", children: b.description || `${b.model}${b.runs ? ` · ${b.runs} runs` : ''}` })] })] }, b.id || b.name));
|
|
360
|
+
})] }), _jsxs(XStack, { px: 16, py: 14, borderTopWidth: 1, borderColor: LINE, items: "center", gap: 12, children: [_jsx(Text, { "aria-hidden": true, display: "flex", width: 32, height: 32, shrink: 0, rounded: 9999, bg: SURFACE, borderWidth: 1, borderColor: LINE, items: "center", justify: "center", fontSize: "$2", fontWeight: "600", color: DIM, children: (user?.name || (live ? '·' : 'Armand Segall')).trim().charAt(0).toUpperCase() }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: INK, children: user?.name || (live ? 'Signed in' : 'Armand Segall') }), _jsx(Text, { ...CLIP, fontSize: "$1", color: FAINT, children: user?.email || (live ? 'Active Workspace' : 'armand@example.com · Example Workspace') })] })] })] }), _jsxs(YStack, { flex: 1, minW: 0, height: "100%", position: "relative", bg: GROUND, "$max-lg": { width: '100%', minW: '100%', shrink: 0, overflowX: 'hidden' }, "$platform-web": { scrollSnapAlign: 'start' }, children: [_jsxs(XStack, { height: 60, borderBottomWidth: 1, borderColor: LINE, items: "center", justify: "space-between", px: 16, gap: 12, children: [_jsxs(XStack, { items: "center", gap: 10, minW: 0, shrink: 1, children: [bot ? _jsx(Face, { bot: bot, size: 28 }) : null, _jsxs(YStack, { minW: 0, shrink: 1, children: [_jsx(Text, { ...CLIP, "data-hz": "head", fontSize: "$3", fontWeight: "600", color: INK, children: bot?.name || 'No bot' }), _jsx(Text, { ...CLIP, "data-hz": "mode", fontSize: "$1", color: FAINT, children: bot ? `${bot.model} · ${bot.executionMode || ONCE}${bot.schedule ? ` · ${bot.schedule}` : ''}` : '' })] })] }), bot ? (_jsxs(XStack, { items: "center", gap: 8, shrink: 0, children: [!always ? (_jsx(Input, { ...BOXED, "data-hz": "cron", value: cron, onChange: (e) => setCron(e.target.value), placeholder: "0 * * * *", "aria-label": "Schedule, as a 5-field cron", width: 104, fontSize: "$1" })) : null, _jsx(Button, { size: "sm", variant: always ? 'primary' : 'default', rounded: 999, title: always ? 'Run only when asked' : 'Keep this bot up on a schedule', "data-hz": "always", onClick: keepUp, "aria-pressed": always, children: "24/7" }), _jsx(Button, { size: "sm", variant: "primary", title: "Open a session for this bot", "data-hz": "open", onClick: async () => {
|
|
361
|
+
if (!client || !bot)
|
|
362
|
+
return;
|
|
363
|
+
setFailed(null);
|
|
364
|
+
try {
|
|
365
|
+
await launch(client, bot.name, `${bot.name} session`);
|
|
366
|
+
await refresh();
|
|
367
|
+
}
|
|
368
|
+
catch (e) {
|
|
369
|
+
setFailed(e instanceof Error ? e.message : String(e));
|
|
370
|
+
}
|
|
371
|
+
}, children: "Open a session" }), _jsx(XStack, { ...GHOST, title: 'What this bot has', "data-hz": "runtime", onClick: () => setPane((p) => !p), "aria-label": "Runtime", "aria-pressed": pane, bg: pane ? SURFACE : 'transparent', children: _jsx(Monitor, { size: 18, color: pane ? INK : DIM }) }), _jsx(XStack, { ...GHOST, title: 'Retire', "data-hz": "retire", onClick: retire, "aria-label": `Retire ${bot.name}`, children: _jsx(Trash2, { size: 16, color: DIM }) })] })) : null] }), _jsxs(XStack, { flex: 1, minH: 0, children: [_jsxs(YStack, { flex: 1, minW: 0, overflow: "hidden", children: [_jsxs(YStack, { "data-hz": "stream", flex: 1, overflowY: "auto", px: 32, py: 24, gap: 16, children: [!bot ? (_jsx(Text, { ...NOTE, children: live
|
|
372
|
+
? 'Nothing is kept yet. A bot is a model, a standing instruction and a record of what it has done — press + to keep one.'
|
|
373
|
+
: 'Select a bot to begin.' })) : null, bot && detail?.instructions ? (_jsxs(YStack, { ...CARD, children: [_jsx(Text, { ...LABEL, children: "Standing instruction" }), _jsx(Text, { render: "p", fontSize: "$2", lineHeight: "1.3rem", color: DIM, whiteSpace: "pre-wrap", children: detail.instructions })] })) : null, computerAction ? (_jsxs(YStack, { ...CARD, "data-hz": "computer-card", children: [_jsxs(XStack, { items: "center", justify: "space-between", children: [_jsxs(XStack, { items: "center", gap: 8, children: [_jsx(Monitor, { size: 15, color: INK }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: INK, children: computerAction.title })] }), _jsx(Text, { display: "inline-flex", items: "center", px: 8, py: 2, rounded: 999, fontSize: "$1", fontWeight: "600", bg: SURFACE, color: DIM, borderWidth: 1, borderColor: LINE, children: computerAction.status })] }), _jsx(Text, { render: "p", fontSize: "$2", lineHeight: "1.26rem", color: INK, children: computerAction.summary }), _jsx(YStack, { gap: 5, mt: 4, children: computerAction.tasks.map((task, idx) => (_jsxs(XStack, { items: "center", gap: 8, children: [_jsx(YStack, { width: 4, height: 4, shrink: 0, rounded: 9999, bg: FAINT }), _jsx(Text, { fontSize: "$2", color: DIM, children: task })] }, idx))) })] })) : null, bot && stream.length === 0 && !computerAction ? (_jsx(Text, { ...NOTE, children: "Nothing has been run yet. Ask for something below and the run is recorded here." })) : null, stream.map(({ key, turn, session }) => session ? (_jsx(Live, { session: session, onChange: refresh }, key)) : turn ? (_jsxs(Text, { render: "div", "data-hz": turn.who === 'you' ? 'asked' : 'answered', self: turn.who === 'you' ? 'flex-end' : 'flex-start', maxW: 620, rounded: 14, px: 16, py: 12, fontSize: "$3", lineHeight: "1.35625rem", whiteSpace: "pre-wrap", bg: turn.who === 'you' ? SURFACE : RAISED, borderWidth: 1, borderColor: LINE, color: turn.failed ? DIM : INK, children: [turn.text, turn.note ? (_jsx(Text, { display: "block", mt: 6, fontSize: "$1", lineHeight: "1.0656rem", color: FAINT, children: turn.failed ? `failed · ${turn.note}` : turn.note })) : null] }, key)) : null), _jsx("div", { ref: foot })] }), _jsxs(YStack, { pt: 16, px: 32, pb: 24, children: [failed ? _jsx(Text, { ...NOTE, px: 0, pt: 0, pb: 8, children: failed }) : null, _jsxs(XStack, { render: "form", onSubmit: run, items: "center", gap: 12, bg: SURFACE, borderWidth: 1, borderColor: LINE, rounded: 26, pt: 6, pr: 8, pb: 6, pl: 16, children: [_jsx(Input, { ...BARE, "data-hz": "ask", value: draft, onChange: (e) => setDraft(e.target.value), placeholder: bot ? `Message ${bot.name}` : 'Message bot...', "aria-label": "Task", disabled: !bot || running, flex: 1, fontSize: "$3" }), _jsx(Button, { type: "submit", variant: "primary", size: "icon", rounded: 9999, title: "Run this task", "data-hz": "run", disabled: !bot || running || !draft.trim(), "aria-label": "Run this task", children: _jsx(Send, { size: 16 }) })] }), _jsx(Text, { render: "p", mt: 8, fontSize: "$1", color: FAINT, children: running
|
|
374
|
+
? 'Running. The recorded run appears above when the platform answers.'
|
|
375
|
+
: bot
|
|
376
|
+
? `Runs on ${bot.model}, recorded against ${bot.name}.`
|
|
377
|
+
: '' })] })] }), pane && bot ? _jsx(Runtime, { bot: bot }) : null] })] })] }));
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* One session of this Bot's, and — while it is still open — the three things
|
|
381
|
+
* the route lets you say to it.
|
|
382
|
+
*
|
|
383
|
+
* A FINISHED SESSION HAS NO CONTROLS. The route accepts a command for one and
|
|
384
|
+
* records it against a log nothing is reading, so drawing Pause beside a run
|
|
385
|
+
* that ended an hour ago is a control over nothing.
|
|
386
|
+
*
|
|
387
|
+
* `forwarded` is the platform's own word for whether a runner was listening: a
|
|
388
|
+
* command is always RECORDED, and only sometimes delivered. Saying which is the
|
|
389
|
+
* difference between a control and a button that appears to work.
|
|
390
|
+
*/
|
|
391
|
+
function Live({ session, onChange }) {
|
|
392
|
+
const { client } = useAi();
|
|
393
|
+
const [said, setSaid] = useState(null);
|
|
394
|
+
const command = async (verb) => {
|
|
395
|
+
if (!client)
|
|
396
|
+
return;
|
|
397
|
+
try {
|
|
398
|
+
const r = await steer(client, session.id, verb);
|
|
399
|
+
setSaid(r.forwarded ? `${verb} delivered` : `${verb} recorded — no runner is attached to this session`);
|
|
400
|
+
await onChange();
|
|
401
|
+
}
|
|
402
|
+
catch (e) {
|
|
403
|
+
setSaid(say(e, 'this session', 'save'));
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
return (_jsxs(YStack, { ...CARD, "data-hz": "session", "data-status": session.status, children: [_jsxs(XStack, { items: "baseline", justify: "space-between", gap: 8, children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: INK, children: session.title || 'Session' }), _jsxs(Text, { fontSize: "$1", color: FAINT, children: [session.status, " \u00B7 ", session.events ?? 0, " turns"] })] }), session.lastEvent?.preview ? (_jsx(Text, { ...CLIP, render: "p", fontSize: "$1", lineHeight: "1.03125rem", fontFamily: "$mono", color: FAINT, children: session.lastEvent.preview })) : null, open(session) ? (_jsxs(XStack, { gap: 6, children: [_jsx(Text, { ...TAB, "data-hz": "pause", onClick: () => command('pause'), children: "Pause" }), _jsx(Text, { ...TAB, "data-hz": "resume", onClick: () => command('resume'), children: "Resume" }), _jsx(Text, { ...TAB, "data-hz": "stop", onClick: () => command('stop'), children: "Stop" })] })) : null, said ? _jsx(Text, { render: "p", fontSize: "$1", color: FAINT, children: said }) : null] }));
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Keeping a Bot.
|
|
410
|
+
*
|
|
411
|
+
* The model is checked against the gateway's served catalogue at create time,
|
|
412
|
+
* so the field OFFERS that catalogue rather than a name this file invented: a
|
|
413
|
+
* model this deployment cannot serve is refused with a 400 naming it, and the
|
|
414
|
+
* platform's own sentence is what a reader is shown.
|
|
415
|
+
*/
|
|
416
|
+
function Hire({ onDone, onCancel }) {
|
|
417
|
+
const { client } = useAi();
|
|
418
|
+
const [name, setName] = useState('');
|
|
419
|
+
const [model, setModel] = useState(ENSO);
|
|
420
|
+
const [about, setAbout] = useState('');
|
|
421
|
+
const [orders, setOrders] = useState('');
|
|
422
|
+
const [busy, setBusy] = useState(false);
|
|
423
|
+
const [failed, setFailed] = useState(null);
|
|
424
|
+
const [served, setServed] = useState([]);
|
|
425
|
+
useEffect(() => {
|
|
426
|
+
if (!client)
|
|
427
|
+
return;
|
|
428
|
+
const stop = new AbortController();
|
|
429
|
+
client.models
|
|
430
|
+
.list({ signal: stop.signal })
|
|
431
|
+
.then(setServed)
|
|
432
|
+
.catch(() => setServed([]));
|
|
433
|
+
return () => stop.abort();
|
|
434
|
+
}, [client]);
|
|
435
|
+
return (_jsxs(YStack, { render: "form", onSubmit: async (e) => {
|
|
436
|
+
e.preventDefault();
|
|
437
|
+
if (!client || busy || !name.trim())
|
|
438
|
+
return;
|
|
439
|
+
setBusy(true);
|
|
440
|
+
setFailed(null);
|
|
441
|
+
try {
|
|
442
|
+
await onDone(await client.agents.create({
|
|
443
|
+
name: name.trim(),
|
|
444
|
+
model: model.trim() || ENSO,
|
|
445
|
+
description: about.trim(),
|
|
446
|
+
instructions: orders.trim(),
|
|
447
|
+
}));
|
|
448
|
+
}
|
|
449
|
+
catch (err) {
|
|
450
|
+
setFailed(err instanceof Error ? err.message : String(err));
|
|
451
|
+
}
|
|
452
|
+
finally {
|
|
453
|
+
setBusy(false);
|
|
454
|
+
}
|
|
455
|
+
}, "data-hz": "hire", mx: 10, mb: 10, p: 12, gap: 8, bg: RAISED, borderWidth: 1, borderColor: LINE, rounded: 10, children: [_jsx(Input, { ...BOXED, "data-hz": "name", value: name, onChange: (e) => setName(e.target.value), placeholder: "Name", "aria-label": "Bot name", autoFocus: true }), _jsx(Input, { ...BOXED, "data-hz": "model", list: "bot-models", value: model, onChange: (e) => setModel(e.target.value), placeholder: "Model", "aria-label": "Model" }), _jsx("datalist", { id: "bot-models", children: served.map((m) => (_jsx("option", { value: m.id }, m.id))) }), _jsx(Input, { ...BOXED, value: about, onChange: (e) => setAbout(e.target.value), placeholder: "What it is for", "aria-label": "Description" }), _jsx(Input, { ...BOXED, render: "textarea", rows: 2, value: orders, onChange: (e) => setOrders(e.target.value), placeholder: "Standing instruction", "aria-label": "Standing instruction" }), failed ? _jsx(Text, { "data-hz": "hire-failed", fontSize: "$1", color: DIM, children: failed }) : null, _jsxs(XStack, { gap: 6, children: [_jsx(Button, { type: "submit", variant: "primary", size: "sm", "data-hz": "keep", disabled: busy || !name.trim(), flex: 1, children: busy ? 'Keeping…' : 'Keep' }), _jsx(Button, { size: "sm", onClick: onCancel, flex: 1, children: "Cancel" })] })] }));
|
|
456
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A window's head: three rings, not three colours. Red, amber and green are
|
|
3
|
+
* another company's mark, and nothing here closes, minimises or zooms, so the
|
|
4
|
+
* shape alone says "window". The Bots room, its capture and the template
|
|
5
|
+
* cards wear it (components/bots/Bots.tsx, components/home/Shot.tsx,
|
|
6
|
+
* components/home/Templates.tsx).
|
|
7
|
+
*/
|
|
8
|
+
export declare function Lights(): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { XStack, YStack } from '@hanzo/gui';
|
|
4
|
+
/**
|
|
5
|
+
* A window's head: three rings, not three colours. Red, amber and green are
|
|
6
|
+
* another company's mark, and nothing here closes, minimises or zooms, so the
|
|
7
|
+
* shape alone says "window". The Bots room, its capture and the template
|
|
8
|
+
* cards wear it (components/bots/Bots.tsx, components/home/Shot.tsx,
|
|
9
|
+
* components/home/Templates.tsx).
|
|
10
|
+
*/
|
|
11
|
+
export function Lights() {
|
|
12
|
+
return (_jsx(XStack, { render: "span", "aria-hidden": true, display: "inline-flex", gap: 6, children: [0, 1, 2].map((i) => (_jsx(YStack, { render: "i", width: 9, height: 9, rounded: 999, borderWidth: 1, borderColor: "var(--border)" }, i))) }));
|
|
13
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { Session, SessionDetail, SessionEvent } from '@hanzo/ai';
|
|
2
|
+
import type { useAi } from '../lib/ai.js';
|
|
3
|
+
export type Client = NonNullable<ReturnType<typeof useAi>['client']>;
|
|
4
|
+
/** The sandbox project that is this Bot's own disk. */
|
|
5
|
+
export declare const disk: (name: string) => string;
|
|
6
|
+
/** The KMS path prefix that is this Bot's own secrets. */
|
|
7
|
+
export declare const vault: (name: string) => string;
|
|
8
|
+
/** Long-running is the platform's word for a Bot that stays up. The set is
|
|
9
|
+
* closed, and long-running is REFUSED without a schedule — measured, 400
|
|
10
|
+
* "a long-running agent requires a 'schedule' (5-field cron)". */
|
|
11
|
+
export declare const ALWAYS = "long-running";
|
|
12
|
+
export declare const ONCE = "one-shot";
|
|
13
|
+
/**
|
|
14
|
+
* The sessions opened under this Bot's name.
|
|
15
|
+
*
|
|
16
|
+
* NARROWED HERE, BECAUSE THE ROUTE WILL NOT. Its query is root, parent,
|
|
17
|
+
* status, project, room and limit — there is no per-agent filter — and an
|
|
18
|
+
* `agent=` it does not know is SILENTLY IGNORED rather than refused. Measured:
|
|
19
|
+
* `?agent=nope` and no query at all answer the same five rows. A caller that
|
|
20
|
+
* trusts the parameter draws the whole org's work as one bot's, which is how
|
|
21
|
+
* this was wrong once already.
|
|
22
|
+
*/
|
|
23
|
+
export declare const sessions: (c: Client, agent: string, limit?: number, signal?: AbortSignal) => Promise<Session[]>;
|
|
24
|
+
/** Opens a session under this Bot's name and answers the row. */
|
|
25
|
+
export declare const launch: (c: Client, agent: string, title: string) => Promise<Session>;
|
|
26
|
+
/** One session with its children and its most recent turns. */
|
|
27
|
+
export declare const transcript: (c: Client, id: string, signal?: AbortSignal) => Promise<SessionDetail>;
|
|
28
|
+
/** Records one turn in a session's log and answers it. */
|
|
29
|
+
export declare const record: (c: Client, id: string, kind: string, text: string) => Promise<SessionEvent>;
|
|
30
|
+
/** The four things you can say to a running session. Each one is recorded as a
|
|
31
|
+
* control turn whether or not a runner is attached to hear it — the reply
|
|
32
|
+
* carries `forwarded`, which says which of the two happened. */
|
|
33
|
+
export declare const steer: (c: Client, id: string, command: 'pause' | 'resume' | 'stop' | 'message', message?: string) => Promise<{
|
|
34
|
+
command?: string;
|
|
35
|
+
event?: SessionEvent;
|
|
36
|
+
forwarded?: boolean;
|
|
37
|
+
}>;
|
|
38
|
+
/** Which Bot answers a channel: the org default and any room bound past it. */
|
|
39
|
+
export interface Answering {
|
|
40
|
+
channel: string;
|
|
41
|
+
default?: string;
|
|
42
|
+
rooms?: Record<string, string>;
|
|
43
|
+
}
|
|
44
|
+
export declare const answering: (c: Client, channel: string, signal?: AbortSignal) => Promise<Answering>;
|
|
45
|
+
/** Makes this Bot the one that answers a channel. */
|
|
46
|
+
export declare const answer: (c: Client, channel: string, agent: string) => Promise<Answering>;
|
|
47
|
+
/**
|
|
48
|
+
* The platform's own MCP surface — every subsystem's operations, projected as
|
|
49
|
+
* tools, over JSON-RPC at `/v1/mcp`.
|
|
50
|
+
*
|
|
51
|
+
* This is where a Bot's callable names actually come from. The OTHER door,
|
|
52
|
+
* `/v1/tools/call`, dispatches only what an org has ACTIVATED in the tool
|
|
53
|
+
* registry, and answers 404 "unknown tool" for everything else — which is
|
|
54
|
+
* everything, for an org that has activated none. So the registry is read for
|
|
55
|
+
* what an org has added and this is read for what the platform already serves.
|
|
56
|
+
*
|
|
57
|
+
* Measured: `tools/list` answers 116 tools for a signed-in caller, and refuses
|
|
58
|
+
* 127 more by a stated rule — a tool is not projected when its name would
|
|
59
|
+
* disclose a bearer secret, or when a mutating verb acts on an identity.
|
|
60
|
+
*/
|
|
61
|
+
export interface McpTool {
|
|
62
|
+
name: string;
|
|
63
|
+
description?: string;
|
|
64
|
+
inputSchema?: unknown;
|
|
65
|
+
annotations?: {
|
|
66
|
+
readOnlyHint?: boolean;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export declare const tools: (c: Client) => Promise<McpTool[]>;
|
|
70
|
+
/** Runs one operation of one tool and answers the text it produced. */
|
|
71
|
+
export declare const call: (c: Client, name: string, op: string, input?: Record<string, unknown>) => Promise<string>;
|
|
72
|
+
/** Starts one autonomous coding run against a repo in the caller's org. */
|
|
73
|
+
export declare const workOn: (c: Client, params: {
|
|
74
|
+
repo: string;
|
|
75
|
+
prompt: string;
|
|
76
|
+
agentRef?: string;
|
|
77
|
+
project?: string;
|
|
78
|
+
}) => Promise<{
|
|
79
|
+
sessionId?: string;
|
|
80
|
+
id?: string;
|
|
81
|
+
}>;
|
|
82
|
+
/** A project the org has declared, with the repo behind it where there is one. */
|
|
83
|
+
export interface Project {
|
|
84
|
+
id: string;
|
|
85
|
+
slug: string;
|
|
86
|
+
name?: string;
|
|
87
|
+
repo?: {
|
|
88
|
+
url?: string;
|
|
89
|
+
branch?: string;
|
|
90
|
+
} | null;
|
|
91
|
+
}
|
|
92
|
+
export declare const projects: (c: Client, signal?: AbortSignal) => Promise<Project[]>;
|
|
93
|
+
/** A secret's NAME and where it lives. The value never leaves KMS. */
|
|
94
|
+
export interface Secret {
|
|
95
|
+
name: string;
|
|
96
|
+
path?: string;
|
|
97
|
+
env?: string;
|
|
98
|
+
scheme?: string;
|
|
99
|
+
}
|
|
100
|
+
export declare const secrets: (c: Client, path: string, signal?: AbortSignal) => Promise<Secret[]>;
|
|
101
|
+
/** A connector as `/v1/integrations/connectors` answers it. Held by the PERSON
|
|
102
|
+
* who signed in to it: the contract publishes no per-agent binding. */
|
|
103
|
+
export interface Connector {
|
|
104
|
+
id: string;
|
|
105
|
+
provider?: string;
|
|
106
|
+
label?: string;
|
|
107
|
+
account?: string;
|
|
108
|
+
scopes?: string[];
|
|
109
|
+
connectedAt?: string;
|
|
110
|
+
expiresAt?: string;
|
|
111
|
+
}
|
|
112
|
+
export declare const connectors: (c: Client, signal?: AbortSignal) => Promise<Connector[]>;
|
|
113
|
+
/** A short-lived grant to open a real terminal or a real screen on a sandbox.
|
|
114
|
+
* The URL it answers is the only address either surface is ever drawn from. */
|
|
115
|
+
export interface Grant {
|
|
116
|
+
url?: string;
|
|
117
|
+
ticket?: string;
|
|
118
|
+
expiresIn?: number;
|
|
119
|
+
}
|
|
120
|
+
export declare const grant: (c: Client, id: string, kind: 'terminal' | 'screen') => Promise<Grant>;
|