@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,229 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useState, useEffect, useMemo } from 'react';
|
|
4
|
+
import { useIam, useIamIdentity, useIamToken, useOrganizations } from '@hanzo/iam/react';
|
|
5
|
+
import { iam } from './api.js';
|
|
6
|
+
const AccountContext = createContext(undefined);
|
|
7
|
+
function mapUser(u) {
|
|
8
|
+
return {
|
|
9
|
+
id: u.id ?? u.name,
|
|
10
|
+
name: u.displayName || u.name,
|
|
11
|
+
email: u.email ?? '',
|
|
12
|
+
avatar: u.avatar,
|
|
13
|
+
phone: u.phone,
|
|
14
|
+
bio: u.bio,
|
|
15
|
+
location: u.location,
|
|
16
|
+
// IAM calls it `homepage`; this app calls it a website. One name each side,
|
|
17
|
+
// translated at the seam rather than in every screen.
|
|
18
|
+
website: u.homepage,
|
|
19
|
+
joinedDate: u.createdTime,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** An organization as the signed `orgs` claim names it: a name and a display name (@hanzo/iam `useOrganizations`). */
|
|
23
|
+
function mapOrg(o, role) {
|
|
24
|
+
return {
|
|
25
|
+
id: o.name,
|
|
26
|
+
name: o.displayName || o.name,
|
|
27
|
+
role: role ?? 'member',
|
|
28
|
+
members: [],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// ── The write ──────────────────────────────────────────────────────────────
|
|
32
|
+
//
|
|
33
|
+
// IAM owns the person. There is no second store to keep a name or a picture in,
|
|
34
|
+
// so a profile edit is a write to IAM and this page has succeeded when that has.
|
|
35
|
+
//
|
|
36
|
+
// `PUT /v1/iam/account` IS THE DOOR, and it is the only one a person has. The
|
|
37
|
+
// request names no target — the subject is whoever holds the token — so no
|
|
38
|
+
// shape of it reaches somebody else's record, and that property is exactly why
|
|
39
|
+
// a regular person may send it. The typed CRUD write beside it binds a WHOLE
|
|
40
|
+
// user from the body, which is an act only an administrator may perform (a
|
|
41
|
+
// full-row write is how a reader would carry `isAdmin` on their own row), so
|
|
42
|
+
// reaching for that one made changing a picture a privilege: the read
|
|
43
|
+
// succeeded, the write was refused, and the picture stayed on screen over a row
|
|
44
|
+
// that had not changed.
|
|
45
|
+
//
|
|
46
|
+
// A PATCH, NOT A ROW. Every field is optional: one omitted keeps the value it
|
|
47
|
+
// had and one sent empty clears it. So the panel that saves a picture cannot
|
|
48
|
+
// blank a biography it never drew, and two screens saving at once do not undo
|
|
49
|
+
// each other — which is also why there is nothing to read first.
|
|
50
|
+
//
|
|
51
|
+
// FOUR FIELDS, because four is what IAM lets a person write about themselves.
|
|
52
|
+
// An email address and a phone number are LOGIN IDENTIFIERS and second-factor
|
|
53
|
+
// destinations: moving where a code is delivered is a credential act, safe only
|
|
54
|
+
// once the new address has proved it can receive one. That is the verification
|
|
55
|
+
// flow's job, not a profile save's, so they are not here and this app does not
|
|
56
|
+
// draw a control that pretends otherwise.
|
|
57
|
+
/** IAM's self-scoped account door, at the host's IAM. */
|
|
58
|
+
const account = () => `${iam()}/v1/iam/account`;
|
|
59
|
+
// ── The read ───────────────────────────────────────────────────────────────
|
|
60
|
+
//
|
|
61
|
+
// THE SAME DOOR, and that is the point: the row this page edits is the row it
|
|
62
|
+
// draws. `PUT /v1/iam/account` names no target and neither does the GET — the
|
|
63
|
+
// subject is whoever holds the token — so a person reads their own record and
|
|
64
|
+
// nobody else's, which is why a regular person may ask.
|
|
65
|
+
//
|
|
66
|
+
// It reads the ROW because a token does not carry one. `IamProvider` sets its
|
|
67
|
+
// `user` to the OIDC userinfo response, which answers who you are (sub, name,
|
|
68
|
+
// email, picture) and nothing about the record: no avatar under that name, no
|
|
69
|
+
// bio, no homepage, no phone, no joined date, and no id. Read as a row it is
|
|
70
|
+
// a person with no picture and empty fields — the profile page drew "Add a
|
|
71
|
+
// picture" over an account that had one.
|
|
72
|
+
/** IAM's answer, whether it wraps the row or is the row. */
|
|
73
|
+
function row(said) {
|
|
74
|
+
if (!said || typeof said !== 'object')
|
|
75
|
+
return null;
|
|
76
|
+
const body = said;
|
|
77
|
+
if (body.status === 'error')
|
|
78
|
+
return null;
|
|
79
|
+
const it = (body.data ?? said);
|
|
80
|
+
return it && typeof it === 'object' ? it : null;
|
|
81
|
+
}
|
|
82
|
+
async function fetchAccount(token) {
|
|
83
|
+
// The bearer is the whole proof, so the request carries no cookie — the same
|
|
84
|
+
// reason the write does not.
|
|
85
|
+
const res = await fetch(account(), { headers: { Authorization: `Bearer ${token}` } });
|
|
86
|
+
if (!res.ok)
|
|
87
|
+
return null;
|
|
88
|
+
return row(await res.json().catch(() => null));
|
|
89
|
+
}
|
|
90
|
+
/** IAM's names for what this app calls a profile. */
|
|
91
|
+
function fields(patch) {
|
|
92
|
+
const body = {};
|
|
93
|
+
if (patch.name !== undefined)
|
|
94
|
+
body.displayName = patch.name;
|
|
95
|
+
if (patch.bio !== undefined)
|
|
96
|
+
body.bio = patch.bio;
|
|
97
|
+
// IAM calls it `homepage`; this app calls it a website. One name each side,
|
|
98
|
+
// translated at the seam rather than in every screen.
|
|
99
|
+
if (patch.website !== undefined)
|
|
100
|
+
body.homepage = patch.website;
|
|
101
|
+
if (patch.avatar !== undefined)
|
|
102
|
+
body.avatar = patch.avatar;
|
|
103
|
+
return body;
|
|
104
|
+
}
|
|
105
|
+
/** What IAM said went wrong. A refused image names its own limit, and that
|
|
106
|
+
* sentence is the useful half of the answer. */
|
|
107
|
+
function reason(said, status) {
|
|
108
|
+
return said?.msg || `Could not save (${status}).`;
|
|
109
|
+
}
|
|
110
|
+
async function store(token, patch) {
|
|
111
|
+
const body = fields(patch);
|
|
112
|
+
// Nothing IAM stores changed, so there is nothing to ask it.
|
|
113
|
+
if (Object.keys(body).length === 0)
|
|
114
|
+
return;
|
|
115
|
+
// THE BEARER IS THE WHOLE PROOF, so the request carries no cookie. IAM opens
|
|
116
|
+
// this path to a browser on the strength of a token in a header — asking for
|
|
117
|
+
// credentials as well means the browser demands an
|
|
118
|
+
// `Access-Control-Allow-Credentials` on the answer, and where the origin does
|
|
119
|
+
// not get one the save fails as a network error with no status and no
|
|
120
|
+
// sentence to show the reader.
|
|
121
|
+
const res = await fetch(account(), {
|
|
122
|
+
method: 'PUT',
|
|
123
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
124
|
+
body: JSON.stringify(body),
|
|
125
|
+
});
|
|
126
|
+
const said = await res.json().catch(() => null);
|
|
127
|
+
if (!res.ok || said?.status === 'error')
|
|
128
|
+
throw new Error(reason(said, res.status));
|
|
129
|
+
}
|
|
130
|
+
export const AccountProvider = ({ children }) => {
|
|
131
|
+
const { user: iamUser, isLoading: authLoading } = useIam();
|
|
132
|
+
const { token } = useIamToken();
|
|
133
|
+
const { organizations: iamOrgs, roles, currentOrgId, switchOrg, isLoading: orgsLoading } = useOrganizations();
|
|
134
|
+
const identity = useIamIdentity();
|
|
135
|
+
// Held against the token it was read with. A row is about ONE person, so a
|
|
136
|
+
// token change must not leave the previous account's picture on screen while
|
|
137
|
+
// the next one is in flight — reading it back through the token is what makes
|
|
138
|
+
// that impossible rather than merely brief.
|
|
139
|
+
const [held, setHeld] = useState(null);
|
|
140
|
+
const account = held && token && held.of === token ? held.row : null;
|
|
141
|
+
const [userOverrides, setUserOverrides] = useState({});
|
|
142
|
+
const [orgOverrides, setOrgOverrides] = useState({});
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
setUserOverrides({});
|
|
145
|
+
setOrgOverrides({});
|
|
146
|
+
}, [iamUser?.id, iamUser?.name]);
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
if (!token)
|
|
149
|
+
return;
|
|
150
|
+
let live = true;
|
|
151
|
+
fetchAccount(token)
|
|
152
|
+
.then((row) => {
|
|
153
|
+
if (live)
|
|
154
|
+
setHeld({ of: token, row });
|
|
155
|
+
})
|
|
156
|
+
// A record that will not load leaves the token's identity on screen,
|
|
157
|
+
// which is what this page had before it asked for the row at all.
|
|
158
|
+
.catch(() => { });
|
|
159
|
+
return () => {
|
|
160
|
+
live = false;
|
|
161
|
+
};
|
|
162
|
+
}, [token]);
|
|
163
|
+
const user = useMemo(() => {
|
|
164
|
+
if (!iamUser && !identity)
|
|
165
|
+
return null;
|
|
166
|
+
// The row where IAM answered with one; otherwise the identity the token
|
|
167
|
+
// already carries, resolved by the one resolver that knows every key IAM
|
|
168
|
+
// spells a name and a picture under.
|
|
169
|
+
const base = account
|
|
170
|
+
? mapUser(account)
|
|
171
|
+
: {
|
|
172
|
+
id: iamUser?.id ?? identity?.name ?? '',
|
|
173
|
+
name: identity?.name ?? '',
|
|
174
|
+
email: identity?.email ?? '',
|
|
175
|
+
avatar: identity?.avatarUrl ?? undefined,
|
|
176
|
+
};
|
|
177
|
+
return { ...base, ...userOverrides };
|
|
178
|
+
}, [account, iamUser, identity, userOverrides]);
|
|
179
|
+
const organizations = useMemo(() => iamOrgs.map((o) => {
|
|
180
|
+
const base = mapOrg(o, roles[o.name]);
|
|
181
|
+
return { ...base, ...orgOverrides[base.id] };
|
|
182
|
+
}), [iamOrgs, roles, orgOverrides]);
|
|
183
|
+
const currentOrganization = useMemo(() => organizations.find((o) => o.id === currentOrgId) ?? organizations[0] ?? null, [organizations, currentOrgId]);
|
|
184
|
+
const setUser = (next) => setUserOverrides(next);
|
|
185
|
+
// The override lands FIRST so the picture the reader just picked is on screen
|
|
186
|
+
// while the request is in flight, and is rolled back where the write is
|
|
187
|
+
// refused — a preview that survives a failure is the page lying about what it
|
|
188
|
+
// has.
|
|
189
|
+
const updateUserProfile = async (userData) => {
|
|
190
|
+
const before = userOverrides;
|
|
191
|
+
setUserOverrides((prev) => ({ ...prev, ...userData }));
|
|
192
|
+
if (!iamUser || !token) {
|
|
193
|
+
setUserOverrides(before);
|
|
194
|
+
throw new Error('Sign in to change your profile.');
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
await store(token, userData);
|
|
198
|
+
}
|
|
199
|
+
catch (e) {
|
|
200
|
+
setUserOverrides(before);
|
|
201
|
+
throw e;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const updateOrganization = (orgData) => {
|
|
205
|
+
if (!currentOrganization)
|
|
206
|
+
return;
|
|
207
|
+
setOrgOverrides((prev) => ({
|
|
208
|
+
...prev,
|
|
209
|
+
[currentOrganization.id]: { ...prev[currentOrganization.id], ...orgData },
|
|
210
|
+
}));
|
|
211
|
+
};
|
|
212
|
+
return (_jsx(AccountContext.Provider, { value: {
|
|
213
|
+
user,
|
|
214
|
+
setUser,
|
|
215
|
+
organizations,
|
|
216
|
+
currentOrganization,
|
|
217
|
+
isLoading: authLoading || orgsLoading,
|
|
218
|
+
switchOrganization: switchOrg,
|
|
219
|
+
updateUserProfile,
|
|
220
|
+
updateOrganization,
|
|
221
|
+
}, children: children }));
|
|
222
|
+
};
|
|
223
|
+
export const useAccount = () => {
|
|
224
|
+
const context = useContext(AccountContext);
|
|
225
|
+
if (context === undefined) {
|
|
226
|
+
throw new Error('useAccount must be used within an AccountProvider');
|
|
227
|
+
}
|
|
228
|
+
return context;
|
|
229
|
+
};
|
package/dist/lib/ai.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type AiClient } from '@hanzo/ai';
|
|
2
|
+
/**
|
|
3
|
+
* The tenant a signed-out turn is recorded against: the publishable key IAM
|
|
4
|
+
* mints, the same one this page beacons analytics with. It names an org and no
|
|
5
|
+
* person, so it is safe in a bundle by construction, and a free turn attributes
|
|
6
|
+
* to the org that published the page rather than to nobody.
|
|
7
|
+
*
|
|
8
|
+
* It is a KEY, not a token, and the distinction decides which route is called.
|
|
9
|
+
* `laneFor` in the SDK asks whether a bearer is present: with one it sends the
|
|
10
|
+
* metered `/v1/chat/completions`, without one it sends `/v1/chat/public` — the
|
|
11
|
+
* credential-less lane, which reads neither the model nor the org off the
|
|
12
|
+
* request, so a signed-out visitor cannot name a paid model or another tenant
|
|
13
|
+
* even by trying. Passing this as `token` would therefore select the metered
|
|
14
|
+
* route and be refused; it is passed as `publishableKey` (SDK >= 0.6.13).
|
|
15
|
+
*/
|
|
16
|
+
/** The gateway's own free pool: enso-free when logged out. */
|
|
17
|
+
export declare const FREE = "enso-free";
|
|
18
|
+
/** Enso routes a signed-in turn: enso-auto when logged in. */
|
|
19
|
+
export declare const ENSO = "enso-auto";
|
|
20
|
+
/**
|
|
21
|
+
* The model that will actually ANSWER, given what the gateway serves.
|
|
22
|
+
*/
|
|
23
|
+
export declare function served(models: {
|
|
24
|
+
id: string;
|
|
25
|
+
}[], prefer: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* The gateway's address in the shape `createAiClient` takes it.
|
|
28
|
+
*
|
|
29
|
+
* The address itself is `api()` — one derivation, in `lib/hanzo/api.ts`, which
|
|
30
|
+
* every plain `fetch` in the app also reads. This working correctly here and
|
|
31
|
+
* nowhere else is what left eight other modules pointed at production while the
|
|
32
|
+
* dev proxy sat unused beside them.
|
|
33
|
+
*/
|
|
34
|
+
export declare const base: () => {
|
|
35
|
+
baseUrl?: string;
|
|
36
|
+
};
|
|
37
|
+
export interface Ai {
|
|
38
|
+
client: AiClient | null;
|
|
39
|
+
model: string;
|
|
40
|
+
/** Answering free, so the turn is data-shared and owes the notice. */
|
|
41
|
+
free: boolean;
|
|
42
|
+
/** Mounted client-side. The static export always prerenders the signed-out
|
|
43
|
+
* page, so nothing interactive may render until this is true. */
|
|
44
|
+
ready: boolean;
|
|
45
|
+
}
|
|
46
|
+
export declare function useAi(): Ai;
|
package/dist/lib/ai.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
// Who answers a chat turn on this site, and on whose account.
|
|
3
|
+
//
|
|
4
|
+
// Signed in — the visitor's own IAM token. Enso routes the turn and it meters to
|
|
5
|
+
// their account like every other turn they run.
|
|
6
|
+
//
|
|
7
|
+
// Signed out — the FREE POOL, anonymously. There is no account to bill and none
|
|
8
|
+
// is needed: a free turn spends no balance, and what it costs instead is stated
|
|
9
|
+
// in the consent block the composer asks before the first send. A widget key, if
|
|
10
|
+
// one is configured, rides along so the turn attributes to the org that minted
|
|
11
|
+
// it; without one the turn is simply anonymous. Either way the client EXISTS,
|
|
12
|
+
// because a chat you cannot send is not a chat.
|
|
13
|
+
//
|
|
14
|
+
// This used to answer `client: null` with no key, which is what put "Sign in to
|
|
15
|
+
// send" under the composer — a wall in front of a lane that is free by design.
|
|
16
|
+
//
|
|
17
|
+
// So `client` is NEVER null now, and the type keeps saying `AiClient | null`
|
|
18
|
+
// because a caller that still branches on it stays correct. Nothing in this
|
|
19
|
+
// file returns null any more; if you are reading the type looking for the
|
|
20
|
+
// signed-out case, it is not there.
|
|
21
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
22
|
+
import { useHydrated } from './hydrated.js';
|
|
23
|
+
import { api } from './api.js';
|
|
24
|
+
import { useIam } from '@hanzo/iam/react';
|
|
25
|
+
import { ORG, bearer, hasSession, org } from './session.js';
|
|
26
|
+
import { createAiClient } from '@hanzo/ai';
|
|
27
|
+
import { observed } from './served.js';
|
|
28
|
+
import { where } from '../where.js';
|
|
29
|
+
/**
|
|
30
|
+
* The tenant a signed-out turn is recorded against: the publishable key IAM
|
|
31
|
+
* mints, the same one this page beacons analytics with. It names an org and no
|
|
32
|
+
* person, so it is safe in a bundle by construction, and a free turn attributes
|
|
33
|
+
* to the org that published the page rather than to nobody.
|
|
34
|
+
*
|
|
35
|
+
* It is a KEY, not a token, and the distinction decides which route is called.
|
|
36
|
+
* `laneFor` in the SDK asks whether a bearer is present: with one it sends the
|
|
37
|
+
* metered `/v1/chat/completions`, without one it sends `/v1/chat/public` — the
|
|
38
|
+
* credential-less lane, which reads neither the model nor the org off the
|
|
39
|
+
* request, so a signed-out visitor cannot name a paid model or another tenant
|
|
40
|
+
* even by trying. Passing this as `token` would therefore select the metered
|
|
41
|
+
* route and be refused; it is passed as `publishableKey` (SDK >= 0.6.13).
|
|
42
|
+
*/
|
|
43
|
+
/** The gateway's own free pool: enso-free when logged out. */
|
|
44
|
+
export const FREE = 'enso-free';
|
|
45
|
+
/** Enso routes a signed-in turn: enso-auto when logged in. */
|
|
46
|
+
export const ENSO = 'enso-auto';
|
|
47
|
+
/**
|
|
48
|
+
* The model that will actually ANSWER, given what the gateway serves.
|
|
49
|
+
*/
|
|
50
|
+
export function served(models, prefer) {
|
|
51
|
+
const cleanPrefer = prefer === 'openrouter/auto' || prefer?.includes('openrouter') ? 'enso-auto' : prefer;
|
|
52
|
+
if (models.length === 0)
|
|
53
|
+
return cleanPrefer || 'enso-auto';
|
|
54
|
+
if (cleanPrefer && models.some((m) => m.id === cleanPrefer))
|
|
55
|
+
return cleanPrefer;
|
|
56
|
+
if (cleanPrefer === 'enso-free' || cleanPrefer === 'free' || cleanPrefer.includes('free')) {
|
|
57
|
+
const free = models.find((m) => m.id === 'enso-free' || m.id === 'zen-free' || (m.id.includes('free') && !m.id.includes('openrouter')));
|
|
58
|
+
if (free)
|
|
59
|
+
return free.id;
|
|
60
|
+
return 'enso-free';
|
|
61
|
+
}
|
|
62
|
+
// House routers: Enso or Zen
|
|
63
|
+
const houseAuto = models.find((m) => m.id === 'enso-auto' || m.id === 'zen-auto' || m.id === 'enso' || m.id === 'zen');
|
|
64
|
+
if (houseAuto)
|
|
65
|
+
return houseAuto.id;
|
|
66
|
+
const houseModel = models.find((m) => (m.id.startsWith('enso') || m.id.startsWith('zen')) && !m.id.includes('openrouter'));
|
|
67
|
+
if (houseModel)
|
|
68
|
+
return houseModel.id;
|
|
69
|
+
const clean = models.find((m) => !m.id.includes('openrouter'));
|
|
70
|
+
if (clean)
|
|
71
|
+
return clean.id;
|
|
72
|
+
return 'enso-auto';
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The gateway's address in the shape `createAiClient` takes it.
|
|
76
|
+
*
|
|
77
|
+
* The address itself is `api()` — one derivation, in `lib/hanzo/api.ts`, which
|
|
78
|
+
* every plain `fetch` in the app also reads. This working correctly here and
|
|
79
|
+
* nowhere else is what left eight other modules pointed at production while the
|
|
80
|
+
* dev proxy sat unused beside them.
|
|
81
|
+
*/
|
|
82
|
+
export const base = () => ({ baseUrl: api() });
|
|
83
|
+
export function useAi() {
|
|
84
|
+
const { sdk, isAuthenticated } = useIam();
|
|
85
|
+
// The organization every call is scoped to: the SDK's selection, sent only
|
|
86
|
+
// when the token says the reader belongs to it (`org()`), and followed through
|
|
87
|
+
// the one event a change fires — so the client is rebuilt and every hook on
|
|
88
|
+
// it reads the new organization's rooms, agents and threads.
|
|
89
|
+
const [scoped, setScoped] = useState(() => org());
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const on = () => setScoped(org());
|
|
92
|
+
window.addEventListener(ORG, on);
|
|
93
|
+
window.addEventListener('storage', on);
|
|
94
|
+
return () => {
|
|
95
|
+
window.removeEventListener(ORG, on);
|
|
96
|
+
window.removeEventListener('storage', on);
|
|
97
|
+
};
|
|
98
|
+
}, []);
|
|
99
|
+
const ready = useHydrated();
|
|
100
|
+
const signedIn = isAuthenticated || hasSession();
|
|
101
|
+
const account = Boolean(signedIn && sdk);
|
|
102
|
+
const client = useMemo(() => {
|
|
103
|
+
// TWO STATES, ONE ISSUER. Signed in is the visitor's own IAM session; signed
|
|
104
|
+
// out is the org's IAM-minted publishable key. There is no third credential
|
|
105
|
+
// and no locally-invented one — a token this app made up is a token the
|
|
106
|
+
// gateway has no reason to accept, and it reaches the metered lane rather
|
|
107
|
+
// than the free one, which is the opposite of what a signed-out reader needs.
|
|
108
|
+
if (account && sdk) {
|
|
109
|
+
return createAiClient({
|
|
110
|
+
fetch: observed((input, init) => fetch(input, init)),
|
|
111
|
+
headers: scoped ? { 'X-Org-Id': scoped } : {},
|
|
112
|
+
auth: {
|
|
113
|
+
...sdk,
|
|
114
|
+
getValidAccessToken: async () => {
|
|
115
|
+
const t = (await sdk.getValidAccessToken?.()) || bearer();
|
|
116
|
+
if (!t)
|
|
117
|
+
return null;
|
|
118
|
+
try {
|
|
119
|
+
const part = t.split('.')[1];
|
|
120
|
+
if (part) {
|
|
121
|
+
const payload = JSON.parse(atob(part.replace(/-/g, '+').replace(/_/g, '/')));
|
|
122
|
+
const auds = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
|
|
123
|
+
if (auds.includes('hanzo-ai') && !auds.includes('hanzo-app')) {
|
|
124
|
+
// Production gateway does not yet whitelist aud="hanzo-ai"; fallback
|
|
125
|
+
// to anonymous key rather than failing with "jwt: audience not allowed".
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch { }
|
|
131
|
+
return t;
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
...base(),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return createAiClient({ fetch: observed((input, init) => fetch(input, init)), publishableKey: where().key ?? '', ...base() });
|
|
138
|
+
}, [account, sdk, scoped]);
|
|
139
|
+
return { client, model: account ? ENSO : FREE, free: !account, ready };
|
|
140
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHERE THE PLATFORM IS, DERIVED ONCE.
|
|
3
|
+
*
|
|
4
|
+
* `hooks/useAi.ts` worked this out correctly for the SDK client and nothing
|
|
5
|
+
* else could reach it: seven other modules wrote their own
|
|
6
|
+
* `process.env.NEXT_PUBLIC_HANZO_API_URL || 'https://api.hanzo.ai'` — reach.ts,
|
|
7
|
+
* team.ts, coding.ts, referrals.ts, useTiers.ts, Settings.tsx, Directory.tsx,
|
|
8
|
+
* Beluga.tsx — and every one of them got the local case wrong in the same way.
|
|
9
|
+
*
|
|
10
|
+
* THE LOCAL CASE IS THE WHOLE POINT. api.hanzo.ai admits an origin by allowlist
|
|
11
|
+
* and by an https, portless DNS proof; localhost satisfies neither, so a
|
|
12
|
+
* credentialed read to an ABSOLUTE address fails its preflight and the room
|
|
13
|
+
* draws as though the org owned nothing. `next.config.mjs` proxies `/v1` for
|
|
14
|
+
* exactly this, but a caller with an absolute default never reaches a rewrite.
|
|
15
|
+
* Same-origin on the dev server is what makes the proxy do its job.
|
|
16
|
+
*
|
|
17
|
+
* A FUNCTION, NOT A CONSTANT. Those eight were module-scope `const`s, evaluated
|
|
18
|
+
* while the static export prerenders — where there is no `window` — so the
|
|
19
|
+
* production address was frozen into the bundle before a browser could say
|
|
20
|
+
* where it was. Called at request time it answers for the page that is open.
|
|
21
|
+
*/
|
|
22
|
+
/** The gateway's origin, with no trailing slash: callers write `${api()}/v1/…`. */
|
|
23
|
+
export declare function api(): string;
|
|
24
|
+
/**
|
|
25
|
+
* Identity, likewise once. IAM issues the bearer every call above carries and
|
|
26
|
+
* is not reached through the API host, so it is a second address rather than a
|
|
27
|
+
* path on the first. It is NOT proxied and does not need to be — hanzo.id
|
|
28
|
+
* admits localhost on authorize, token and userinfo.
|
|
29
|
+
*
|
|
30
|
+
* `NEXT_PUBLIC_HANZO_IAM_URL` is the name — the one `.env.example` documents
|
|
31
|
+
* and `AccountContext` already reads. `team.ts` read `NEXT_PUBLIC_HANZO_ID_URL`
|
|
32
|
+
* instead, a name nothing sets, so pointing this app at another IAM would have
|
|
33
|
+
* moved the account panel and left the invitation links behind on hanzo.id.
|
|
34
|
+
* Two spellings of one setting are indistinguishable from a working one until
|
|
35
|
+
* the day it is changed.
|
|
36
|
+
*/
|
|
37
|
+
export declare function iam(): string;
|
|
38
|
+
/**
|
|
39
|
+
* The IAM application hanzo.ai signs people in through, and the one an invited
|
|
40
|
+
* member joins through: the gateway admits it and IAM holds hanzo.ai's callbacks
|
|
41
|
+
* on it. It is platform-owned, so its full id is `admin/<CLIENT>`.
|
|
42
|
+
*/
|
|
43
|
+
export declare const CLIENT = "hanzo-app";
|
package/dist/lib/api.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHERE THE PLATFORM IS, DERIVED ONCE.
|
|
3
|
+
*
|
|
4
|
+
* `hooks/useAi.ts` worked this out correctly for the SDK client and nothing
|
|
5
|
+
* else could reach it: seven other modules wrote their own
|
|
6
|
+
* `process.env.NEXT_PUBLIC_HANZO_API_URL || 'https://api.hanzo.ai'` — reach.ts,
|
|
7
|
+
* team.ts, coding.ts, referrals.ts, useTiers.ts, Settings.tsx, Directory.tsx,
|
|
8
|
+
* Beluga.tsx — and every one of them got the local case wrong in the same way.
|
|
9
|
+
*
|
|
10
|
+
* THE LOCAL CASE IS THE WHOLE POINT. api.hanzo.ai admits an origin by allowlist
|
|
11
|
+
* and by an https, portless DNS proof; localhost satisfies neither, so a
|
|
12
|
+
* credentialed read to an ABSOLUTE address fails its preflight and the room
|
|
13
|
+
* draws as though the org owned nothing. `next.config.mjs` proxies `/v1` for
|
|
14
|
+
* exactly this, but a caller with an absolute default never reaches a rewrite.
|
|
15
|
+
* Same-origin on the dev server is what makes the proxy do its job.
|
|
16
|
+
*
|
|
17
|
+
* A FUNCTION, NOT A CONSTANT. Those eight were module-scope `const`s, evaluated
|
|
18
|
+
* while the static export prerenders — where there is no `window` — so the
|
|
19
|
+
* production address was frozen into the bundle before a browser could say
|
|
20
|
+
* where it was. Called at request time it answers for the page that is open.
|
|
21
|
+
*/
|
|
22
|
+
import { guest, team } from './host.js';
|
|
23
|
+
import { where } from '../where.js';
|
|
24
|
+
/** The gateway's origin, with no trailing slash: callers write `${api()}/v1/…`. */
|
|
25
|
+
export function api() {
|
|
26
|
+
const set = where().api;
|
|
27
|
+
if (set)
|
|
28
|
+
return set.replace(/\/+$/, '');
|
|
29
|
+
if (typeof window !== 'undefined') {
|
|
30
|
+
const host = window.location.hostname;
|
|
31
|
+
// The shipped site is served from an origin the gateway already admits, so
|
|
32
|
+
// there the absolute address stands and no proxy is involved. hanzo.team,
|
|
33
|
+
// hanzo.bot and the Sites plane's `<slug>.hanzo.app` aliases serve this same
|
|
34
|
+
// export and are admitted the same way; none of them has a /v1 of its own.
|
|
35
|
+
if (host === 'hanzo.ai' || host.endsWith('.hanzo.ai') || team(host) || host === 'hanzo.bot' || guest(host))
|
|
36
|
+
return 'https://api.hanzo.ai';
|
|
37
|
+
return window.location.origin;
|
|
38
|
+
}
|
|
39
|
+
return 'https://api.hanzo.ai';
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Identity, likewise once. IAM issues the bearer every call above carries and
|
|
43
|
+
* is not reached through the API host, so it is a second address rather than a
|
|
44
|
+
* path on the first. It is NOT proxied and does not need to be — hanzo.id
|
|
45
|
+
* admits localhost on authorize, token and userinfo.
|
|
46
|
+
*
|
|
47
|
+
* `NEXT_PUBLIC_HANZO_IAM_URL` is the name — the one `.env.example` documents
|
|
48
|
+
* and `AccountContext` already reads. `team.ts` read `NEXT_PUBLIC_HANZO_ID_URL`
|
|
49
|
+
* instead, a name nothing sets, so pointing this app at another IAM would have
|
|
50
|
+
* moved the account panel and left the invitation links behind on hanzo.id.
|
|
51
|
+
* Two spellings of one setting are indistinguishable from a working one until
|
|
52
|
+
* the day it is changed.
|
|
53
|
+
*/
|
|
54
|
+
export function iam() {
|
|
55
|
+
return (where().iam || 'https://hanzo.id').replace(/\/+$/, '');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The IAM application hanzo.ai signs people in through, and the one an invited
|
|
59
|
+
* member joins through: the gateway admits it and IAM holds hanzo.ai's callbacks
|
|
60
|
+
* on it. It is platform-owned, so its full id is `admin/<CLIENT>`.
|
|
61
|
+
*/
|
|
62
|
+
export const CLIENT = 'hanzo-app';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A scroller that does not draw its scrollbar, as a gui prop: a row of chips or
|
|
3
|
+
* tabs scrolls sideways under a finger or a trackpad, and a bar under it would
|
|
4
|
+
* be chrome around nothing. gui applies `scrollbarWidth` on the web and its
|
|
5
|
+
* types do not list it, so it is spread rather than written.
|
|
6
|
+
*/
|
|
7
|
+
export declare const bare: {
|
|
8
|
+
readonly scrollbarWidth: 'none';
|
|
9
|
+
};
|
package/dist/lib/bare.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A scroller that does not draw its scrollbar, as a gui prop: a row of chips or
|
|
3
|
+
* tabs scrolls sideways under a finger or a trackpad, and a bar under it would
|
|
4
|
+
* be chrome around nothing. gui applies `scrollbarWidth` on the web and its
|
|
5
|
+
* types do not list it, so it is spread rather than written.
|
|
6
|
+
*/
|
|
7
|
+
export const bare = { scrollbarWidth: 'none' };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starting a coding run, and choosing WHERE it runs.
|
|
3
|
+
*
|
|
4
|
+
* The contract, read off the served document rather than assumed:
|
|
5
|
+
* - start POST /v1/agents/coding → 202 {sessionId, repo, branch, routed, targetId}
|
|
6
|
+
* - targets GET /v1/agents/targets → {targets: [...]}
|
|
7
|
+
*
|
|
8
|
+
* WHERE A RUN RUNS IS ONE FIELD. Omit `targetId` and the run goes to the cloud's
|
|
9
|
+
* own sandbox; name one and it is routed to a machine the org has claimed. The
|
|
10
|
+
* answer comes back rather than being assumed: `routed` says whether it reached
|
|
11
|
+
* a machine, and `targetId` says which. A caller that asked for a machine and
|
|
12
|
+
* reads `routed: false` was given a sandbox instead, and should say so rather
|
|
13
|
+
* than draw a screen that claims otherwise.
|
|
14
|
+
*
|
|
15
|
+
* That is the whole of the web-vs-desktop question. A browser has no machine to
|
|
16
|
+
* offer, so it sends no target and gets a sandbox. A surface running ON a
|
|
17
|
+
* claimed machine sends that machine's id. Neither needs a second endpoint, and
|
|
18
|
+
* nothing here sniffs the user agent to decide — the caller says what it has.
|
|
19
|
+
*
|
|
20
|
+
* A refusal states its reason: 401 is the credential and 403 is permission, and
|
|
21
|
+
* neither is "try again", so neither says it.
|
|
22
|
+
*
|
|
23
|
+
* AND IT REACHES THE READER, which for a while it did not. On localhost this
|
|
24
|
+
* caught every refusal it had just raised and answered with a fabricated
|
|
25
|
+
* accepted run — `sessionId: 'ses_' + Math.random()` — plus two invented
|
|
26
|
+
* targets, and `token()` minted `hz_local_dev_token` for a reader who was not
|
|
27
|
+
* signed in. So the one machine the acceptance gate is run on was the one where
|
|
28
|
+
* a platform that was down, unauthenticated or 500ing still opened a run, wrote
|
|
29
|
+
* `?run=` and mounted live Pause/Resume/Stop over nothing. Measured: the same
|
|
30
|
+
* prompt twice returned two different ids, which a served id cannot do.
|
|
31
|
+
*
|
|
32
|
+
* `Start.tsx` already renders `e.message` beside the composer. Point
|
|
33
|
+
* NEXT_PUBLIC_HANZO_API_URL at a platform that answers; do not synthesise one.
|
|
34
|
+
*/
|
|
35
|
+
/** What a coding run answers with. 202 — accepted, not finished. */
|
|
36
|
+
export interface CodingRun {
|
|
37
|
+
/** The session every surface hangs this run's activity off. */
|
|
38
|
+
sessionId: string;
|
|
39
|
+
repo: string;
|
|
40
|
+
/** The branch the run works on; the server picks one when `base` is empty. */
|
|
41
|
+
branch: string;
|
|
42
|
+
/** Whether it reached a claimed machine. False means the sandbox took it. */
|
|
43
|
+
routed: boolean;
|
|
44
|
+
/** Which machine, when one took it. Empty for a sandbox run. */
|
|
45
|
+
targetId: string;
|
|
46
|
+
}
|
|
47
|
+
/** A machine the org has claimed, as `/v1/agents/targets` lists it. */
|
|
48
|
+
export interface Target {
|
|
49
|
+
/** `tgt_` + 32 hex. What a run names to route here. */
|
|
50
|
+
id: string;
|
|
51
|
+
/** The name a person gave it ("workshop"). */
|
|
52
|
+
label: string;
|
|
53
|
+
/** laptop | cloud | … — a closed set the server owns. */
|
|
54
|
+
kind: string;
|
|
55
|
+
/** EFFECTIVE liveness: online | offline | draining. */
|
|
56
|
+
status: string;
|
|
57
|
+
/** Human summary — "8 vCPU / 32G", "1× GB10". */
|
|
58
|
+
capacity?: string;
|
|
59
|
+
host?: string;
|
|
60
|
+
/** How many of the org's sessions map here, and how many are running now. */
|
|
61
|
+
sessions?: number;
|
|
62
|
+
running?: number;
|
|
63
|
+
}
|
|
64
|
+
export interface StartCoding {
|
|
65
|
+
/** The task, in the words you would use with a colleague. */
|
|
66
|
+
prompt: string;
|
|
67
|
+
/** Model to route the coding agent through. */
|
|
68
|
+
model?: string;
|
|
69
|
+
/** `owner/name` in the caller's own org. */
|
|
70
|
+
repo?: string;
|
|
71
|
+
/** dev | claude | codex | python — which harness runs the prompt. */
|
|
72
|
+
tool?: string;
|
|
73
|
+
/** The branch to start from. Empty takes the repository's default. */
|
|
74
|
+
base?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Route to a claimed machine. OMITTED IS THE SANDBOX, and that is the default
|
|
77
|
+
* on the web deliberately: a browser has no machine to offer.
|
|
78
|
+
*/
|
|
79
|
+
targetId?: string;
|
|
80
|
+
/** Ask for a run with a screen — an image carrying an X server. */
|
|
81
|
+
desktop?: boolean;
|
|
82
|
+
}
|
|
83
|
+
export declare class CodingRefusal extends Error {
|
|
84
|
+
readonly status: number;
|
|
85
|
+
constructor(status: number, message: string);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Starts one autonomous coding run and answers the session it opened.
|
|
89
|
+
*
|
|
90
|
+
* 202, not 200: the run is ACCEPTED here and finishes later, so what comes back
|
|
91
|
+
* is where to watch it — `sessionId` is the row the working view already
|
|
92
|
+
* renders — and never its output.
|
|
93
|
+
*/
|
|
94
|
+
export declare function startCoding(params: StartCoding): Promise<CodingRun>;
|
|
95
|
+
export declare const startRun: typeof startCoding;
|
|
96
|
+
/**
|
|
97
|
+
* The machines this org has claimed.
|
|
98
|
+
*
|
|
99
|
+
* An empty list is an ANSWER — this org has claimed none, so every run goes to
|
|
100
|
+
* a sandbox — and is not an error. A refusal still throws, because "we could
|
|
101
|
+
* not ask" and "there are none" must not render the same.
|
|
102
|
+
*/
|
|
103
|
+
export declare function listTargets(): Promise<Target[]>;
|
|
104
|
+
/** A machine can take a run only while it is online. */
|
|
105
|
+
export declare const dispatchable: (t: Target) => boolean;
|