@columenlabs/lumen-ai 0.1.254 → 0.1.255
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/README.md +1 -1
- package/dist/assistant/engine.js +20 -22
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -111,7 +111,7 @@ app.all('/api/assistant/*', (c) => routes(c.req.raw));
|
|
|
111
111
|
- **Who pays.** The person's pick (the shared keys by default) while it has a key for the tier's provider and money left this month under its cap (an admin sets the shared keys' cap per person, `PUT /app`), else the other; with neither the panel says why.
|
|
112
112
|
- **Tools.** Each group is Off, Read, Read + draft or Read + write per person. A tool is a read when its MCP `readOnlyHint` is set. On Read + draft a write only drafts a card, and runs as the person when they press Save, after access is checked again. A group with `readOnly: true` (read tools only) is offered Off or Read alone. Read + write is offered only for a group with `writes: true`: its writes run at once, and with `undo` set each one's line in the chat has Undo, which calls `undo(caller, { tool, args, result })`; the model is told what was undone. Cards drafted one after another show as one stack with Save all. Use `mcpServerTools` (an SDK `McpServer`), `mcpFetchTools` (a server served as a fetch handler, any SDK), `remoteMcpTools` or `plainTools`.
|
|
113
113
|
- **Web.** `fetch` is a plain GET from the Worker (10 s, 2 MB, never the app's own hosts or private addresses). ScrapeQL's own tools join over its MCP API once an admin pastes a Cloudflare Access service token (`PUT /web/scrapeql`). A Worker on ScrapeQL's own Cloudflare account passes its service binding instead, `scrapeql: { url: 'https://scrapeql/mcp', fetch: env.SCRAPEQL.fetch.bind(env.SCRAPEQL) }`: no token is stored, the tools are always on and Settings shows the link with a Test. Each web step carries `web` (`StepWeb`: a search's `query` and `results`, or the page it `read`), so the panel folds a search's results under its line and numbers each page read under the answer as Sources chips; the model marks facts with those numbers ([1]).
|
|
114
|
-
- **Web lookup (a subagent).** `web: { scrapeql, lookup: true }` swaps fetch and ScrapeQL's own tools for one tool, `web__lookup` (`webLookupTools` from `@columenlabs/lumen-ai/subagents`): given a question (and `urls` when the pages are known), it searches with ScrapeQL, has ScrapeQL's `ask` read the best pages (3 by default, `pages` up to 5) and answer in a few sentences each (`answerTokens`, default 300), and returns `{question, sources: [{url, title, answer}], failed}`: about a thousand tokens, never whole pages, so the assistant's context stays small. No model runs in the kit for it; ScrapeQL picks its own cheapest model. Its step carries `web.lookup` and `web.reads`, which the panel folds under a Looked up line and numbers as Sources chips. `fetch.blockHosts` still applies.
|
|
114
|
+
- **Web lookup (a subagent).** `web: { scrapeql, lookup: true }` swaps fetch and ScrapeQL's own tools for one tool, `web__lookup` (`webLookupTools` from `@columenlabs/lumen-ai/subagents`): given a question (and `urls` when the pages are known), it searches with ScrapeQL, has ScrapeQL's `ask` read the best pages (3 by default, `pages` up to 5) and answer in a few sentences each (`answerTokens`, default 300), and returns `{question, sources: [{url, title, answer}], failed}`: about a thousand tokens, never whole pages, so the assistant's context stays small. No model runs in the kit for it; ScrapeQL picks its own cheapest model. Its step carries `web.lookup` and `web.reads`, which the panel folds under a Looked up line and numbers as Sources chips. `fetch.blockHosts` still applies. Until ScrapeQL is reachable (no binding and no Access token saved yet), the web falls back to `fetch` and the lookup rule is left out, so turning lookup on never takes the web away.
|
|
115
115
|
- **Hand off and advice.** The `escalate` tool hands one task to a higher tier (the person sets next, any or only when asked); Anthropic's advisor tool lets a higher tier send a plan, at most two an answer. A hand-off gives up after `HAND_OFF_MS` (4 minutes) and the turn goes on without it.
|
|
116
116
|
- **Chats** run on the agent (`./agent`, `LumenAgent`, below): the app routes `POST /chat` and each chat's reads, cards and Undo to its own agent, and `assistantRoutes` answers `POST /chat` with 410 `moved`. Chats saved by the old turn loop stay readable (`GET /chats`, `GET|DELETE /chats/:id`) until `assistant.purge()` drops them 30 days after their last message.
|
|
117
117
|
- **Screen moves**: while the person's Settings ▸ Screen is Move (the default), the panel sends its page map (`AssistantMessage.screen`: routes, nav pages, Settings sections, cards on screen, rows to open) and `LumenAgent` gives the model a `screen` tool (`./agent` `screen.ts`). Each call's moves (go, settings, chart, open, point) are checked against the map, kept in the chat and streamed as a `screen` event; the panel runs them through `AssistantScreenProvider` (mounted by `LumenShell`) with an Undo chip, and their outcomes go back with the next message (`outcomes`). Nothing is saved by a move.
|
package/dist/assistant/engine.js
CHANGED
|
@@ -118,6 +118,7 @@ function openedSummary(t) {
|
|
|
118
118
|
reads: t.reads.length > 0,
|
|
119
119
|
batch: true,
|
|
120
120
|
web: t.info.some((i) => i.group === 'web'),
|
|
121
|
+
lookup: t.info.some((i) => i.name === 'web__lookup'),
|
|
121
122
|
};
|
|
122
123
|
}
|
|
123
124
|
export class Assistant {
|
|
@@ -827,36 +828,33 @@ export class Assistant {
|
|
|
827
828
|
for (const source of Array.isArray(s) ? s : [s])
|
|
828
829
|
sources.push({ group: g.id, access, source, ...(g.writes ? { writes: true } : {}) });
|
|
829
830
|
}
|
|
830
|
-
|
|
831
|
-
const blockHosts = this.o.web?.fetch ? this.o.web.fetch.blockHosts : undefined;
|
|
832
|
-
if ((p.web ?? true) && lookup) {
|
|
831
|
+
if (p.web ?? true) {
|
|
833
832
|
const sq = this.o.web?.scrapeql;
|
|
834
833
|
const token = sq && !sq.fetch ? await this.state.scrapeql() : null;
|
|
835
|
-
|
|
834
|
+
const scrapeql = sq && (sq.fetch || token) ? scrapeqlTools(sq.url, token, sq.fetch ?? this.o.fetch) : null;
|
|
835
|
+
const lookup = this.o.web?.lookup;
|
|
836
|
+
if (lookup && scrapeql) {
|
|
837
|
+
// The one lookup tool. Without ScrapeQL (no binding, no token yet) the web falls back to fetch.
|
|
838
|
+
const blockHosts = this.o.web?.fetch ? this.o.web.fetch.blockHosts : undefined;
|
|
836
839
|
sources.push({
|
|
837
840
|
group: 'web',
|
|
838
841
|
access: 'read',
|
|
839
|
-
source: webLookupTools(
|
|
842
|
+
source: webLookupTools(scrapeql, {
|
|
840
843
|
...(blockHosts ? { blockHosts } : {}),
|
|
841
844
|
...(typeof lookup === 'object' ? lookup : {}),
|
|
842
845
|
}),
|
|
843
846
|
});
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
sources.push({
|
|
856
|
-
group: 'web',
|
|
857
|
-
access: 'read',
|
|
858
|
-
source: scrapeqlTools(sq.url, token, sq.fetch ?? this.o.fetch),
|
|
859
|
-
});
|
|
847
|
+
}
|
|
848
|
+
else {
|
|
849
|
+
if (this.o.web?.fetch !== false)
|
|
850
|
+
sources.push({
|
|
851
|
+
group: 'web',
|
|
852
|
+
access: 'read',
|
|
853
|
+
source: webFetchTools(this.o.web?.fetch || {}),
|
|
854
|
+
});
|
|
855
|
+
if (scrapeql)
|
|
856
|
+
sources.push({ group: 'web', access: 'read', source: scrapeql });
|
|
857
|
+
}
|
|
860
858
|
}
|
|
861
859
|
return sources;
|
|
862
860
|
}
|
|
@@ -887,7 +885,7 @@ export class Assistant {
|
|
|
887
885
|
lines.push('No tools are on now: the person can turn them on in Settings ▸ Tools.');
|
|
888
886
|
if (failed)
|
|
889
887
|
lines.push(`${failed} tool source${failed === 1 ? '' : 's'} could not be reached this turn.`);
|
|
890
|
-
if (fns.
|
|
888
|
+
if (fns.lookup)
|
|
891
889
|
lines.push("Web: web__lookup answers one question from the web: give the full question (and urls when you already know the pages); it returns a short answer from each page it read. Look up again with a sharper question rather than asking for whole pages. The person sees each page a lookup read numbered under your answer, in the order returned (the first lookup's sources first, a later lookup's after them): mark a fact with its number, like [1], and never cite a page that is not among the sources.");
|
|
892
890
|
else if (fns.web)
|
|
893
891
|
lines.push('Web: use web__fetch first for APIs and plain pages; the browser tools only for pages that need JavaScript or after a fetch fails. To find pages, search, then read the one to three that answer the question. The person sees each page you read numbered under your answer, in the order you read them (1 for the first page read in this answer): mark a fact you took from a page with its number, like [1], and never cite a page you did not read.');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.255",
|
|
4
4
|
"description": "Lumen's assistant and agent parts: a chat assistant's server (Claude tiers, tools over MCP, the web, hand off, spend caps), serving an MCP server from a Worker, read-only tool sets and metered tool calls.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
"@ai-sdk/openai": "4.0.108",
|
|
57
57
|
"@anthropic-ai/sdk": "0.133.0",
|
|
58
58
|
"@cloudflare/think": "0.23.0",
|
|
59
|
-
"@columenlabs/lumen-ops": "0.1.
|
|
60
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
59
|
+
"@columenlabs/lumen-ops": "0.1.255",
|
|
60
|
+
"@columenlabs/lumen-ui-types": "0.1.255",
|
|
61
61
|
"agents": "0.32.0",
|
|
62
62
|
"ai": "7.0.162",
|
|
63
63
|
"just-bash": "3.6.0",
|