@hanzo/rooms 0.1.76 → 0.1.77
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/dist/Chat.js +63 -10
- package/dist/lib/attach.d.ts +8 -1
- package/dist/lib/attach.js +14 -1
- package/dist/lib/web.d.ts +0 -6
- package/dist/lib/web.js +7 -39
- package/package.json +3 -3
package/dist/Chat.js
CHANGED
|
@@ -33,7 +33,7 @@ import { Prose } from "./Prose.js";
|
|
|
33
33
|
import { useAgent, useAi, useChat, } from "@hanzo/ai/react";
|
|
34
34
|
import { freeCopy, grantConsent, hasConsent, isFree, } from "@hanzo/ai";
|
|
35
35
|
import { ASK, Chat as ChatSurface, Failure, Parts, words } from "@hanzo/ui/chat";
|
|
36
|
-
import { carriedIn, compose, partsOf, said as told, weigh } from "./lib/attach.js";
|
|
36
|
+
import { carriedIn, carrying, compose, partsOf, said as told, weigh } from "./lib/attach.js";
|
|
37
37
|
import { download, dropped, fileOf, pct, readable, refOf, retrieve } from "./lib/files.js";
|
|
38
38
|
import { channel, drop, hold, landed, localOf, spend, take, usePane, useWorkFile } from "./pane.js";
|
|
39
39
|
import { Face, brief as roomBrief, join, roleOf, roomTurn, roster as rosterLine, rosterOf, speakers, speaksOf, voiceOf } from "./cast.js";
|
|
@@ -49,7 +49,7 @@ import { app, dev, go } from "./lib/host.js";
|
|
|
49
49
|
import { useRooms } from "./host.js";
|
|
50
50
|
import { chats, house, paidPlan, paused, setCreditsAfterAllowance, useLimits } from "./lib/limits.js";
|
|
51
51
|
import { LimitedBanner } from "@hanzo/ui/product/LimitedBanner";
|
|
52
|
-
import {
|
|
52
|
+
import { WEB } from "./lib/web.js";
|
|
53
53
|
import { useHydrated } from "./lib/hydrated.js";
|
|
54
54
|
import { base, served } from "./lib/ai.js";
|
|
55
55
|
import { onServed } from "./lib/served.js";
|
|
@@ -516,7 +516,17 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
516
516
|
// What the conversation carries beside its words (@hanzo/build `useCarry`):
|
|
517
517
|
// the skills picked from `+`, what Hanzo remembers and the mode, said at the
|
|
518
518
|
// end of the room's system turn so every request carries them.
|
|
519
|
-
|
|
519
|
+
// What the plugins act on — the draft and the files held for the next message —
|
|
520
|
+
// read when they act, so it is lent through a ref filled further down.
|
|
521
|
+
const lent = useRef({ draft: "", setDraft: () => { }, attach: () => { } });
|
|
522
|
+
const carry = useCarry(target, open ?? "", (tab) => go(app(`-/customize/${tab}`)), {
|
|
523
|
+
get draft() {
|
|
524
|
+
return lent.current.draft;
|
|
525
|
+
},
|
|
526
|
+
setDraft: (x) => lent.current.setDraft(x),
|
|
527
|
+
attach: (f) => lent.current.attach(f),
|
|
528
|
+
go: (href) => (/^https?:/.test(href) ? void window.open(href, "_blank", "noopener,noreferrer") : go(href)),
|
|
529
|
+
});
|
|
520
530
|
const agentShelf = useAgentShelf();
|
|
521
531
|
// Up to three things worth doing next, from the person's own activity (@hanzo/build).
|
|
522
532
|
const hints = useSuggestions(target);
|
|
@@ -716,7 +726,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
716
726
|
// world, in any language, is answered from what the web says now and streams.
|
|
717
727
|
params: {
|
|
718
728
|
reasoning_effort: mind.effort,
|
|
719
|
-
...(!anonymous && house(rowOf(model)?.family) ? { tools:
|
|
729
|
+
...(!anonymous && house(rowOf(model)?.family) ? { tools: WEB } : {}),
|
|
720
730
|
},
|
|
721
731
|
...(open ? { thread: open } : {}),
|
|
722
732
|
onError: (e) => {
|
|
@@ -854,6 +864,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
854
864
|
// read from it for the question.
|
|
855
865
|
const { held: files, refused: turnedAway } = usePane(at);
|
|
856
866
|
const via = useMemo(() => ({ api: ai, org: anonymous ? null : org() }), [ai, anonymous]);
|
|
867
|
+
lent.current = { draft, setDraft, attach: (f) => void hold(at, f, via) };
|
|
857
868
|
// "Use in Chat" from the Library: `?attach=<item>` holds it for the next message.
|
|
858
869
|
useAttach(at, via);
|
|
859
870
|
// "Schedule this": the prompt the dialog opens on, or null while it is shut.
|
|
@@ -1140,15 +1151,55 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1140
1151
|
* while; past it the turn says the file is not readable yet rather than
|
|
1141
1152
|
* answering as though it had been read.
|
|
1142
1153
|
*/
|
|
1154
|
+
// WHAT THE PLUGINS THAT ARE ON ADD, asked just before a message goes: words
|
|
1155
|
+
// the model reads with it (carried in the turn's own block, as files are), or
|
|
1156
|
+
// an answer of the plugin's own, drawn as the reply without asking a model.
|
|
1157
|
+
const [prepWrong, setPrepWrong] = useState(null);
|
|
1158
|
+
const answered = (text, reply) => {
|
|
1159
|
+
apply.current((held) => [
|
|
1160
|
+
...held,
|
|
1161
|
+
{ id: `u-${Date.now()}`, role: "user", content: text },
|
|
1162
|
+
{ id: `a-${Date.now()}`, role: "assistant", content: reply },
|
|
1163
|
+
]);
|
|
1164
|
+
if (!anonymous)
|
|
1165
|
+
void ai.threads
|
|
1166
|
+
.record([{ role: "user", content: text }, { role: "assistant", content: reply }], open ?? undefined)
|
|
1167
|
+
.then((id) => (!open && id ? reopen(id) : undefined))
|
|
1168
|
+
.catch(() => { });
|
|
1169
|
+
};
|
|
1143
1170
|
const submit = () => {
|
|
1144
1171
|
const text = draft.trim();
|
|
1145
1172
|
if (preparing || (!text && !files.length))
|
|
1146
1173
|
return;
|
|
1174
|
+
if (!carry.preparing)
|
|
1175
|
+
return deliver(text, "");
|
|
1176
|
+
setDraft("");
|
|
1177
|
+
setPrepWrong(null);
|
|
1178
|
+
void (async () => {
|
|
1179
|
+
setPreparing("Preparing");
|
|
1180
|
+
try {
|
|
1181
|
+
// The files held for this message, as bytes, for a plugin that reads them (PDF).
|
|
1182
|
+
const bytes = await Promise.all(files.filter((h) => h.href).map(async (h) => new File([await (await fetch(h.href)).blob()], h.name, { type: h.type })));
|
|
1183
|
+
const got = await carry.prep({ text, files: bytes, thread: open ?? "" }, new AbortController().signal);
|
|
1184
|
+
if (got.reply && !got.system)
|
|
1185
|
+
return answered(text, got.reply);
|
|
1186
|
+
deliver(text, got.system ?? "");
|
|
1187
|
+
}
|
|
1188
|
+
catch (e) {
|
|
1189
|
+
setPrepWrong(e instanceof Error ? e.message : String(e));
|
|
1190
|
+
setDraft(text);
|
|
1191
|
+
}
|
|
1192
|
+
finally {
|
|
1193
|
+
setPreparing(null);
|
|
1194
|
+
}
|
|
1195
|
+
})();
|
|
1196
|
+
};
|
|
1197
|
+
const deliver = (text, extra) => {
|
|
1147
1198
|
const holding = files.slice();
|
|
1148
1199
|
const earlier = carriedIn(chat.messages);
|
|
1149
1200
|
if (!holding.length && !earlier.length) {
|
|
1150
1201
|
setDraft("");
|
|
1151
|
-
guarded(text);
|
|
1202
|
+
guarded(extra ? carrying(text, extra) : text);
|
|
1152
1203
|
return;
|
|
1153
1204
|
}
|
|
1154
1205
|
setDraft("");
|
|
@@ -1163,7 +1214,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1163
1214
|
const all = [...attached, ...reused];
|
|
1164
1215
|
if (!all.length) {
|
|
1165
1216
|
if (text)
|
|
1166
|
-
guarded(text);
|
|
1217
|
+
guarded(extra ? carrying(text, extra) : text);
|
|
1167
1218
|
else
|
|
1168
1219
|
setDraft(text);
|
|
1169
1220
|
return;
|
|
@@ -1180,7 +1231,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1180
1231
|
}
|
|
1181
1232
|
if (!grounds?.passages.length)
|
|
1182
1233
|
why = why ?? unread(now);
|
|
1183
|
-
guarded(compose(text, { attached, reused }, grounds, why, partly(now)));
|
|
1234
|
+
guarded(compose(text, { attached, reused }, grounds, why, partly(now), extra));
|
|
1184
1235
|
}
|
|
1185
1236
|
finally {
|
|
1186
1237
|
setPreparing(null);
|
|
@@ -1257,7 +1308,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1257
1308
|
// just typed left the screen, and the first thing a stranger saw of the
|
|
1258
1309
|
// product was a page about training data. The room stays; the sheet sits on
|
|
1259
1310
|
// top of it, and the question is still there behind it.
|
|
1260
|
-
const consent = asking === null ? null : (_jsx(Consent, { question: asking, onAgree: () => {
|
|
1311
|
+
const consent = asking === null ? null : (_jsx(Consent, { question: told(asking).text, onAgree: () => {
|
|
1261
1312
|
const s = store();
|
|
1262
1313
|
// Nothing to persist into is not a reason to refuse the turn the
|
|
1263
1314
|
// reader just agreed to; it means the question is asked again next
|
|
@@ -1426,7 +1477,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1426
1477
|
},
|
|
1427
1478
|
// WHAT GOES WITH THE MESSAGE, above the frame and at its width: the
|
|
1428
1479
|
// pause and its ways past, one chip a file, and talk mode's words.
|
|
1429
|
-
head: files.length || turnedAway || preparing || talk.open || talk.refusal || pause ? (_jsxs(_Fragment, { children: [banner ? (_jsx(LimitedBanner, { message: banner.message, actions: banner.actions, onAction: act, onUsage: () => showSettings("usage") })) : null, actWrong ? (_jsx(Text, { role: "alert", "data-slot": "limited-banner-wrong", fontSize: "$2", color: BAD, children: actWrong })) : null, files.length ? (_jsx(XStack, { gap: "$1.5", flexWrap: "wrap", role: "list", "aria-label": "Attached files", "data-slot": "composer-attached", children: files.map((one) => (_jsx(HeldChip, { one: one, onOpen: openFile, onRemove: () => drop(at, one.id), onSave: anonymous ? undefined : (url, id) => keepOne(url, id, one.name, one.type) }, one.id))) })) : null, preparing ? (_jsxs(Text, { role: "status", "aria-live": "polite", "data-slot": "composer-preparing", fontSize: "$2", color: "$soft", children: [preparing, "\u2026"] })) : null, turnedAway ? (_jsx(Text, { role: "alert", fontSize: "$2", color: BAD, children: turnedAway })) : null, talk.open || talk.refusal ? (_jsx(Text, { role: "status", "aria-live": "polite", "data-slot": "composer-talk-reply", fontSize: "$2", color: talk.refusal ? BAD : "$soft", children: talk.refusal
|
|
1480
|
+
head: files.length || turnedAway || prepWrong || preparing || talk.open || talk.refusal || pause ? (_jsxs(_Fragment, { children: [banner ? (_jsx(LimitedBanner, { message: banner.message, actions: banner.actions, onAction: act, onUsage: () => showSettings("usage") })) : null, actWrong ? (_jsx(Text, { role: "alert", "data-slot": "limited-banner-wrong", fontSize: "$2", color: BAD, children: actWrong })) : null, files.length ? (_jsx(XStack, { gap: "$1.5", flexWrap: "wrap", role: "list", "aria-label": "Attached files", "data-slot": "composer-attached", children: files.map((one) => (_jsx(HeldChip, { one: one, onOpen: openFile, onRemove: () => drop(at, one.id), onSave: anonymous ? undefined : (url, id) => keepOne(url, id, one.name, one.type) }, one.id))) })) : null, preparing ? (_jsxs(Text, { role: "status", "aria-live": "polite", "data-slot": "composer-preparing", fontSize: "$2", color: "$soft", children: [preparing, "\u2026"] })) : null, turnedAway || prepWrong ? (_jsx(Text, { role: "alert", fontSize: "$2", color: BAD, children: turnedAway || prepWrong })) : null, talk.open || talk.refusal ? (_jsx(Text, { role: "status", "aria-live": "polite", "data-slot": "composer-talk-reply", fontSize: "$2", color: talk.refusal ? BAD : "$soft", children: talk.refusal
|
|
1430
1481
|
? worded(talk.refusal)
|
|
1431
1482
|
: talk.reply
|
|
1432
1483
|
? `Hanzo: ${talk.reply}`
|
|
@@ -1441,6 +1492,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1441
1492
|
upload: () => take(at, via),
|
|
1442
1493
|
agents: agentShelf,
|
|
1443
1494
|
skills: carry.skills,
|
|
1495
|
+
plugins: carry.plugins,
|
|
1444
1496
|
research: { on: carry.gear.gear.research, set: (on) => carry.gear.set({ research: on }) },
|
|
1445
1497
|
memory: { on: carry.gear.gear.memory, set: (on) => carry.gear.set({ memory: on }) },
|
|
1446
1498
|
},
|
|
@@ -1455,6 +1507,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1455
1507
|
...(anonymous ? [] : carry.on),
|
|
1456
1508
|
],
|
|
1457
1509
|
slash: anonymous ? undefined : carry.skills,
|
|
1510
|
+
at: anonymous ? undefined : carry.plugins,
|
|
1458
1511
|
mode: anonymous ? null : { value: carry.gear.gear.mode, set: (m) => carry.gear.set({ mode: m }) },
|
|
1459
1512
|
// The model, the effort and the mode under the frame (@hanzo/build).
|
|
1460
1513
|
mind,
|
|
@@ -1466,7 +1519,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
|
|
|
1466
1519
|
talk,
|
|
1467
1520
|
// While nothing has been said: what is worth doing next.
|
|
1468
1521
|
below: said.length === 0 && !anonymous ? _jsx(Suggestions, { rows: hints.rows, onPick: setDraft, onDismiss: hints.dismiss }) : undefined,
|
|
1469
|
-
}) }), _jsx(AgentMaker, {}), isFree(model) ? (_jsx(XStack, { items: "center", justify: "center", py: "$1", px: "$3", borderTopWidth: 1, borderColor: "$borderColor", bg: "$background", children: _jsxs(Text, { fontSize: "$1", color: "$soft", children: ["Free chat is data-shared to train models. ", _jsx(Anchor, { href: site("/privacy"), fontSize: "$1", color: "$ink", children: "Privacy" })] }) })) : null, consent, _jsx(Upgrade, { ask: ask, onClose: () => setAsk(null) })] }));
|
|
1522
|
+
}) }), _jsx(AgentMaker, {}), carry.dialog, isFree(model) ? (_jsx(XStack, { items: "center", justify: "center", py: "$1", px: "$3", borderTopWidth: 1, borderColor: "$borderColor", bg: "$background", children: _jsxs(Text, { fontSize: "$1", color: "$soft", children: ["Free chat is data-shared to train models. ", _jsx(Anchor, { href: site("/privacy"), fontSize: "$1", color: "$ink", children: "Privacy" })] }) })) : null, consent, _jsx(Upgrade, { ask: ask, onClose: () => setAsk(null) })] }));
|
|
1470
1523
|
}
|
|
1471
1524
|
/** The agreement free asks for, in @hanzo/ai's words so every surface says one thing. */
|
|
1472
1525
|
function Consent({ question, onAgree, onCancel, }) {
|
package/dist/lib/attach.d.ts
CHANGED
|
@@ -28,7 +28,14 @@ export declare function weigh(bytes: number): string;
|
|
|
28
28
|
* stored without indexing, or the read failed — so the model says so rather
|
|
29
29
|
* than answering as though it had read the files.
|
|
30
30
|
*/
|
|
31
|
-
export declare function compose(text: string, carried: Carried, grounds: Grounds | null, why?: string, partial?: string): string;
|
|
31
|
+
export declare function compose(text: string, carried: Carried, grounds: Grounds | null, why?: string, partial?: string, extra?: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* The message as sent with what a plugin found for it (research, a PDF's text):
|
|
34
|
+
* the words, then the same block files ride in, holding no files, so it is
|
|
35
|
+
* drawn as the words alone and `lookup` reads the turn as carried, never
|
|
36
|
+
* searching it a second time.
|
|
37
|
+
*/
|
|
38
|
+
export declare function carrying(text: string, extra: string): string;
|
|
32
39
|
/** A turn read back: the words a person wrote, and the files it carried. */
|
|
33
40
|
export interface Said {
|
|
34
41
|
text: string;
|
package/dist/lib/attach.js
CHANGED
|
@@ -38,7 +38,7 @@ const inert = (s) => s.split(CLOSE).join('</workspace files>');
|
|
|
38
38
|
* stored without indexing, or the read failed — so the model says so rather
|
|
39
39
|
* than answering as though it had read the files.
|
|
40
40
|
*/
|
|
41
|
-
export function compose(text, carried, grounds, why, partial) {
|
|
41
|
+
export function compose(text, carried, grounds, why, partial, extra) {
|
|
42
42
|
const words = text.trim();
|
|
43
43
|
const all = [...carried.attached, ...carried.reused];
|
|
44
44
|
if (!all.length)
|
|
@@ -62,10 +62,23 @@ export function compose(text, carried, grounds, why, partial) {
|
|
|
62
62
|
else {
|
|
63
63
|
lines.push(`No passages could be read from them${why ? ` (${why})` : ''}: say so, and answer only what you can without them.`);
|
|
64
64
|
}
|
|
65
|
+
if (extra)
|
|
66
|
+
lines.push('', inert(extra));
|
|
65
67
|
lines.push(CLOSE);
|
|
66
68
|
const block = lines.join('\n');
|
|
67
69
|
return words ? `${words}\n\n${block}` : block;
|
|
68
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* The message as sent with what a plugin found for it (research, a PDF's text):
|
|
73
|
+
* the words, then the same block files ride in, holding no files, so it is
|
|
74
|
+
* drawn as the words alone and `lookup` reads the turn as carried, never
|
|
75
|
+
* searching it a second time.
|
|
76
|
+
*/
|
|
77
|
+
export function carrying(text, extra) {
|
|
78
|
+
const words = text.trim();
|
|
79
|
+
const block = [OPEN, JSON.stringify({ attached: [], reused: [] }), inert(extra), CLOSE].join('\n');
|
|
80
|
+
return words ? `${words}\n\n${block}` : block;
|
|
81
|
+
}
|
|
69
82
|
/** Takes the files block back off a turn, for drawing. */
|
|
70
83
|
export function said(content) {
|
|
71
84
|
const at = content.startsWith(`${OPEN}\n`) ? 0 : content.lastIndexOf(`\n${OPEN}\n`);
|
package/dist/lib/web.d.ts
CHANGED
|
@@ -37,12 +37,6 @@
|
|
|
37
37
|
import { type ChatCompletionTool } from '@hanzo/ai';
|
|
38
38
|
/** Pages a turn that names them reads. */
|
|
39
39
|
export declare const PAGES = 3;
|
|
40
|
-
/**
|
|
41
|
-
* Research: beside `WEB`, a turn that carries this reads deeper — ten results
|
|
42
|
-
* listed, five pages read — whatever its shape, and the model is asked for a
|
|
43
|
-
* report that cites them.
|
|
44
|
-
*/
|
|
45
|
-
export declare const RESEARCH: ChatCompletionTool;
|
|
46
40
|
/** The two tools that mark a turn as one that may use the web. */
|
|
47
41
|
export declare const WEB: ChatCompletionTool[];
|
|
48
42
|
/**
|
package/dist/lib/web.js
CHANGED
|
@@ -41,9 +41,6 @@ export const PAGES = 3;
|
|
|
41
41
|
/** Results a search lists, and how many of them it reads. */
|
|
42
42
|
const LISTED = 6;
|
|
43
43
|
const READ = 2;
|
|
44
|
-
/** What Research lists and reads instead. */
|
|
45
|
-
const DEEP_LISTED = 10;
|
|
46
|
-
const DEEP_READ = 5;
|
|
47
44
|
/** Characters of a page a search quotes, and a named page quotes. */
|
|
48
45
|
const PAGE_CHARS = 3500;
|
|
49
46
|
const CRAWL_CHARS = 6000;
|
|
@@ -56,21 +53,6 @@ const GATE = 8192;
|
|
|
56
53
|
const SEARCH_MS = 20_000;
|
|
57
54
|
const SKIM_MS = 10_000;
|
|
58
55
|
const READ_MS = 25_000;
|
|
59
|
-
/**
|
|
60
|
-
* Research: beside `WEB`, a turn that carries this reads deeper — ten results
|
|
61
|
-
* listed, five pages read — whatever its shape, and the model is asked for a
|
|
62
|
-
* report that cites them.
|
|
63
|
-
*/
|
|
64
|
-
export const RESEARCH = {
|
|
65
|
-
type: 'function',
|
|
66
|
-
function: {
|
|
67
|
-
name: 'research',
|
|
68
|
-
description: 'Research the question in depth on the live web and answer with a cited report.',
|
|
69
|
-
parameters: { type: 'object', properties: {}, required: [] },
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
/** The word a Research turn's model is given. */
|
|
73
|
-
const REPORT = 'Research mode: answer with a detailed report — a short direct answer first, then the evidence under headings — citing each claim with the numbered sources above as [n](url). Say plainly where the sources disagree or fall short.';
|
|
74
56
|
/** The two tools that mark a turn as one that may use the web. */
|
|
75
57
|
export const WEB = [
|
|
76
58
|
{
|
|
@@ -545,7 +527,7 @@ function pointed(text) {
|
|
|
545
527
|
/** What the model is told of a page it was not given. */
|
|
546
528
|
const withheld = (url) => `The person's message names ${label(url)}. It was not opened: opening an address like it can sign in, confirm, unsubscribe or spend a one-time link for them.`;
|
|
547
529
|
/** Runs one search: the results, best first, and the text of the best pages. */
|
|
548
|
-
async function search(call, q, lang, now
|
|
530
|
+
async function search(call, q, lang, now) {
|
|
549
531
|
let found;
|
|
550
532
|
try {
|
|
551
533
|
found = await call('/v1/websearch', lang ? { q, language: lang } : { q }, SEARCH_MS);
|
|
@@ -554,11 +536,11 @@ async function search(call, q, lang, now, deep = false) {
|
|
|
554
536
|
return unavailable(e);
|
|
555
537
|
}
|
|
556
538
|
const words = keywords(q);
|
|
557
|
-
const ranked = rank(found?.results ?? [], words).slice(0,
|
|
539
|
+
const ranked = rank(found?.results ?? [], words).slice(0, LISTED);
|
|
558
540
|
const head = `Web search for "${q}", ${stamp(now)}.`;
|
|
559
541
|
if (!ranked.length)
|
|
560
542
|
return `${head}\nNothing was found.`;
|
|
561
|
-
const pages = await Promise.all(ranked.slice(0,
|
|
543
|
+
const pages = await Promise.all(ranked.slice(0, READ).map((r) => call('/v1/crawl', { url: r.url }, SKIM_MS)
|
|
562
544
|
.then((d) => (d?.success ? excerpt(d.data?.markdown ?? '', words, PAGE_CHARS) : ''))
|
|
563
545
|
.catch(() => '')));
|
|
564
546
|
const items = ranked.map((r, i) => {
|
|
@@ -581,19 +563,6 @@ async function read(call, url, words) {
|
|
|
581
563
|
return `Could not read ${url}: ${e instanceof Error ? e.message : String(e)}.`;
|
|
582
564
|
}
|
|
583
565
|
}
|
|
584
|
-
/** A Research turn is looked up whatever its shape: its own words, searched. Files and pictures still stay home. */
|
|
585
|
-
function forced(messages, spoken) {
|
|
586
|
-
const last = messages.at(-1);
|
|
587
|
-
if (last?.role !== 'user')
|
|
588
|
-
return null;
|
|
589
|
-
const { text: said, carried } = told(textOf(last.content));
|
|
590
|
-
if (carried || pictured(last.content))
|
|
591
|
-
return null;
|
|
592
|
-
const plain = spell(said.trim().slice(0, GATE)).trim();
|
|
593
|
-
if (!plain)
|
|
594
|
-
return null;
|
|
595
|
-
return { pages: [], held: [], q: plain.slice(0, QUERY), lang: language(plain, spoken), about: keywords(plain) };
|
|
596
|
-
}
|
|
597
566
|
/**
|
|
598
567
|
* The lookup the last turn needs, or null. A short follow-up ("and in
|
|
599
568
|
* Moscow?") is read with the question before it, the way the person meant it.
|
|
@@ -698,8 +667,7 @@ export function researched(base, clock = () => new Date(), spoken = reader) {
|
|
|
698
667
|
if (!asked.stream || !Array.isArray(asked.messages) || !WEB.every((t) => names.has(t.function.name)))
|
|
699
668
|
return base(input, init);
|
|
700
669
|
const { tools: _tools, tool_choice: _choice, ...bare } = asked;
|
|
701
|
-
const
|
|
702
|
-
const look = lookup(asked.messages, spoken()) ?? (deep ? forced(asked.messages, spoken()) : null);
|
|
670
|
+
const look = lookup(asked.messages, spoken());
|
|
703
671
|
if (!look)
|
|
704
672
|
return base(input, { ...init, body: JSON.stringify(bare) });
|
|
705
673
|
// The reader's stop, and the stream's own cancel, end every request below.
|
|
@@ -727,10 +695,10 @@ export function researched(base, clock = () => new Date(), spoken = reader) {
|
|
|
727
695
|
};
|
|
728
696
|
const found = (await Promise.all([
|
|
729
697
|
...look.pages.map((u) => read(call, u, look.about)),
|
|
730
|
-
...(look.q ? [search(call, look.q, look.lang, now
|
|
698
|
+
...(look.q ? [search(call, look.q, look.lang, now)] : []),
|
|
731
699
|
...look.held.map(async (u) => withheld(u)),
|
|
732
700
|
])).join('\n\n');
|
|
733
|
-
const messages = primed(asked.messages, found, now
|
|
701
|
+
const messages = primed(asked.messages, found, now);
|
|
734
702
|
const first = await send(messages);
|
|
735
703
|
if (!streams(first))
|
|
736
704
|
return first;
|
|
@@ -741,7 +709,7 @@ export function researched(base, clock = () => new Date(), spoken = reader) {
|
|
|
741
709
|
// results: asked once more, it answers. One that failed is not asked again.
|
|
742
710
|
const { said, failed } = await relay(first, out);
|
|
743
711
|
if (!said.trim() && !failed) {
|
|
744
|
-
const again = await send(primed(asked.messages, found, now,
|
|
712
|
+
const again = await send(primed(asked.messages, found, now, SILENT));
|
|
745
713
|
if (streams(again))
|
|
746
714
|
await relay(again, out);
|
|
747
715
|
else
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/rooms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.77",
|
|
4
4
|
"description": "The workspace rooms — Home, Chat, Inbox, Contacts, Meet, Cal, Drive, Board, Work, Bots — and the frame that holds them, once, for every Hanzo surface.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@hanzo/ai": ">=0.6.23",
|
|
51
51
|
"@hanzo/appearance": ">=0.2.6",
|
|
52
|
-
"@hanzo/build": ">=0.2.
|
|
52
|
+
"@hanzo/build": ">=0.2.64",
|
|
53
53
|
"@hanzo/composer": ">=0.2.0",
|
|
54
54
|
"@hanzo/event": ">=0.3.56",
|
|
55
55
|
"@hanzo/gui": "^8.3.5",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@hanzo/ai": "0.6.23",
|
|
69
69
|
"@hanzo/appearance": "0.2.8",
|
|
70
|
-
"@hanzo/build": "0.2.
|
|
70
|
+
"@hanzo/build": "0.2.64",
|
|
71
71
|
"@hanzo/composer": "0.2.0",
|
|
72
72
|
"@hanzo/design": "0.5.34",
|
|
73
73
|
"@hanzo/event": "0.3.56",
|