eve-hub 0.6.63 → 0.6.65
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/CHANGELOG.md +12 -0
- package/onboarding/files.gen.mjs +1 -1
- package/onboarding/github-facts.mjs +5 -5
- package/package.json +1 -1
- package/world.mjs +36 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# eve-hub
|
|
2
2
|
|
|
3
|
+
## 0.6.65
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- The onboarding-profile GitHub script runs on the `gh` in the sandbox (2.46): it no longer passes `--slurp`, which `gh` has only from 2.48.
|
|
8
|
+
|
|
9
|
+
## 0.6.64
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Hosted hub: who takes part in a thread is read with one index seek per bot, not by reading every message in it, so a send or a fold costs the bots in the thread; the reads the feed, sweep and runner polls repeat now use indexes and cost what is waiting, not what the workspace holds.
|
|
14
|
+
|
|
3
15
|
## 0.6.63
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/onboarding/files.gen.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by scripts/build-onboarding.mjs. Do not edit.
|
|
2
|
-
export default {"github-facts.mjs":"#!/usr/bin/env node\n// Facts about the signed-in `gh` user's last 90 days on GitHub, as one JSON document on stdout.\n// Counts, titles (<=200 chars), URLs and CODEOWNERS lines only; no judgment: the model reads this and profiles.\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { pathToFileURL } from \"node:url\";\nimport { snippet } from \"./redact.mjs\";\n\nconst exec = promisify(execFile);\nconst DAYS = 90;\nconst LIST_CAP = 150;\nconst CODEOWNERS_REPOS = 10;\nconst STATEFUL = new Set([\"open\", \"reviewRequested\"]);\nconst CODEOWNERS_PATHS = [\".github/CODEOWNERS\", \"CODEOWNERS\", \"docs/CODEOWNERS\"];\n\nconst BASE = `\n __typename\n ... on PullRequest { number title url createdAt updatedAt mergedAt isDraft additions deletions changedFiles repository { nameWithOwner } author { login } @STATE@ }\n ... on Issue { number title url createdAt updatedAt repository { nameWithOwner } author { login } }`;\nconst STATE = \"reviewDecision mergeable reviewRequests { totalCount } commits(last: 1) { nodes { commit { statusCheckRollup { state } } } }\";\nconst searchQuery = (withState) => `query($q: String!, $endCursor: String) {\n search(query: $q, type: ISSUE, first: 50, after: $endCursor) {\n issueCount\n pageInfo { hasNextPage endCursor }\n nodes {${BASE.replace(\"@STATE@\", withState ? STATE : \"\")}}\n }\n}`;\n\nexport const realRun = async (args) => (await exec(\"gh\", args, { maxBuffer: 1 << 28 })).stdout;\n\nconst ATTEMPTS = 3;\n\nasync function search(run, q, withState = false) {\n const args = [\"api\", \"graphql\", \"--paginate\", \"--
|
|
2
|
+
export default {"github-facts.mjs":"#!/usr/bin/env node\n// Facts about the signed-in `gh` user's last 90 days on GitHub, as one JSON document on stdout.\n// Counts, titles (<=200 chars), URLs and CODEOWNERS lines only; no judgment: the model reads this and profiles.\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { pathToFileURL } from \"node:url\";\nimport { snippet } from \"./redact.mjs\";\n\nconst exec = promisify(execFile);\nconst DAYS = 90;\nconst LIST_CAP = 150;\nconst CODEOWNERS_REPOS = 10;\nconst STATEFUL = new Set([\"open\", \"reviewRequested\"]);\nconst CODEOWNERS_PATHS = [\".github/CODEOWNERS\", \"CODEOWNERS\", \"docs/CODEOWNERS\"];\n\nconst BASE = `\n __typename\n ... on PullRequest { number title url createdAt updatedAt mergedAt isDraft additions deletions changedFiles repository { nameWithOwner } author { login } @STATE@ }\n ... on Issue { number title url createdAt updatedAt repository { nameWithOwner } author { login } }`;\nconst STATE = \"reviewDecision mergeable reviewRequests { totalCount } commits(last: 1) { nodes { commit { statusCheckRollup { state } } } }\";\nconst searchQuery = (withState) => `query($q: String!, $endCursor: String) {\n search(query: $q, type: ISSUE, first: 50, after: $endCursor) {\n issueCount\n pageInfo { hasNextPage endCursor }\n nodes {${BASE.replace(\"@STATE@\", withState ? STATE : \"\")}}\n }\n}`;\n\nexport const realRun = async (args) => (await exec(\"gh\", args, { maxBuffer: 1 << 28 })).stdout;\n\nconst ATTEMPTS = 3;\n\nasync function search(run, q, withState = false) {\n const args = [\"api\", \"graphql\", \"--paginate\", \"--jq\", \".data.search\", \"-f\", `query=${searchQuery(withState)}`, \"-f\", `q=${q}`];\n let out;\n for (let attempt = 1; ; attempt++) {\n try {\n out = await run(args);\n break;\n } catch (e) {\n if (attempt === ATTEMPTS) throw e;\n await new Promise((r) => setTimeout(r, 1000 * attempt + Math.random() * 1000));\n }\n }\n const pages = out.split(\"\\n\").filter(Boolean).map((l) => JSON.parse(l));\n const nodes = pages.flatMap((p) => p.nodes).filter((n) => n?.url);\n return { total: pages[0].issueCount, nodes };\n}\n\nconst item = (n) => ({\n repo: n.repository.nameWithOwner,\n number: n.number,\n type: n.__typename,\n title: snippet(n.title),\n url: n.url,\n author: n.author?.login ?? null,\n createdAt: n.createdAt,\n updatedAt: n.updatedAt,\n ...(n.__typename === \"PullRequest\"\n ? {\n mergedAt: n.mergedAt,\n draft: n.isDraft,\n additions: n.additions,\n deletions: n.deletions,\n changedFiles: n.changedFiles,\n reviewDecision: n.reviewDecision ?? null,\n mergeable: n.mergeable ?? null,\n checks: n.commits?.nodes?.[0]?.commit?.statusCheckRollup?.state ?? null,\n reviewRequests: n.reviewRequests?.totalCount ?? null,\n }\n : {}),\n});\n\nconst byRecent = (a, b) => (b.mergedAt ?? b.updatedAt).localeCompare(a.mergedAt ?? a.updatedAt);\nconst listed = ({ total, nodes }) => ({ total, listed: Math.min(nodes.length, LIST_CAP), items: nodes.map(item).sort(byRecent).slice(0, LIST_CAP) });\n\nfunction perRepo(sets) {\n const repos = {};\n for (const [key, nodes] of Object.entries(sets)) {\n for (const n of nodes) {\n const r = (repos[n.repository.nameWithOwner] ??= { repo: n.repository.nameWithOwner, merged: 0, reviewed: 0, open: 0, additions: 0, deletions: 0 });\n r[key] += 1;\n if (key === \"merged\") { r.additions += n.additions ?? 0; r.deletions += n.deletions ?? 0; }\n }\n }\n return Object.values(repos).sort((a, b) => b.merged + b.reviewed + b.open - (a.merged + a.reviewed + a.open));\n}\n\nasync function codeownersLines(run, repo, login, teams) {\n const owners = [`@${login}`, ...teams.map((t) => `@${t}`)].map((o) => o.toLowerCase());\n for (const path of CODEOWNERS_PATHS) {\n let text;\n try {\n text = await run([\"api\", `repos/${repo}/contents/${path}`, \"-H\", \"Accept: application/vnd.github.raw+json\"]);\n } catch (e) {\n if (/HTTP 404/.test(e.stderr ?? e.message)) continue;\n throw e;\n }\n return text.split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l && !l.startsWith(\"#\") && l.toLowerCase().split(/\\s+/).some((tok) => owners.includes(tok)))\n .map((line) => ({ repo, path, line: snippet(line) }));\n }\n return [];\n}\n\nexport async function collect({ run = realRun, now = new Date() } = {}) {\n const since = new Date(now.getTime() - DAYS * 86400_000).toISOString().slice(0, 10);\n const unreadable = [];\n const attempt = async (what, fn, fallback) => {\n try {\n return await fn();\n } catch (e) {\n unreadable.push({ what, error: snippet(e.stderr || e.message) });\n return fallback;\n }\n };\n const empty = { total: 0, nodes: [] };\n const login = JSON.parse(await run([\"api\", \"user\"])).login;\n const teams = await attempt(\"user teams\", async () => (await run([\"api\", \"user/teams\", \"--paginate\", \"--jq\", '.[] | .organization.login + \"/\" + .slug'])).split(\"\\n\").filter(Boolean), []);\n const q = {\n merged: `is:pr author:@me is:merged merged:>=${since} archived:false`,\n open: \"is:pr author:@me is:open archived:false\",\n reviewRequested: \"is:pr is:open review-requested:@me archived:false\",\n assignedIssues: \"is:issue is:open assignee:@me archived:false\",\n unansweredMentions: `is:open mentions:@me -commenter:@me -author:@me updated:>=${since} archived:false`,\n reviewed: `is:pr reviewed-by:@me -author:@me merged:>=${since} archived:false`,\n };\n const got = Object.fromEntries(await Promise.all(Object.entries(q).map(async ([k, v]) => [k, await attempt(`search ${k}`, () => search(run, v, STATEFUL.has(k)), empty)])));\n const repos = perRepo({ merged: got.merged.nodes, reviewed: got.reviewed.nodes, open: got.open.nodes });\n const owned = (await Promise.all(repos.slice(0, CODEOWNERS_REPOS).map((r) => attempt(`CODEOWNERS ${r.repo}`, () => codeownersLines(run, r.repo, login, teams), [])))).flat();\n return {\n login,\n window: { since, until: now.toISOString().slice(0, 10), days: DAYS },\n counts: Object.fromEntries(Object.entries(got).map(([k, v]) => [k, v.total])),\n repos: repos.slice(0, 30),\n merged: listed(got.merged),\n open: listed(got.open),\n reviewRequested: listed(got.reviewRequested),\n assignedIssues: listed(got.assignedIssues),\n unansweredMentions: listed(got.unansweredMentions),\n teams,\n codeowners: owned,\n unreadable,\n };\n}\n\nif (import.meta.url === pathToFileURL(process.argv[1] ?? \"\").href) {\n process.stdout.write(JSON.stringify(await collect(), null, 1) + \"\\n\");\n}\n","redact.mjs":"const TOKEN = /\\b(?:xox[a-z]-[A-Za-z0-9-]{8,}|sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{16,}|github_pat_[A-Za-z0-9_]{16,}|vck_[A-Za-z0-9]{16,}|AKIA[A-Z0-9]{16}|eyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]*)/g;\n\nexport const MAX_TEXT = 200;\n\nexport function snippet(text) {\n const flat = String(text ?? \"\").replace(/\\s+/g, \" \").trim().replace(TOKEN, \"[redacted]\");\n return flat.length > MAX_TEXT ? flat.slice(0, MAX_TEXT - 1) + \"…\" : flat;\n}\n","check-report.mjs":"#!/usr/bin/env node\n// Checks the model-written profile against the facts it came from:\n// --profile p.md --facts a.json [b.json...] every URL in the profile is in the facts, and every list item cites one\n// Exits 1 and prints one line per problem.\nimport { readFileSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\n\nconst URL_RE = /https?:\\/\\/[^\\s)>\\]\"'`]+/g;\nconst trimUrl = (u) => u.replace(/[.,;:!?]+$/, \"\");\n\nexport function factUrls(facts) {\n const urls = new Set();\n const walk = (v) => {\n if (typeof v === \"string\") { if (/^https?:\\/\\//.test(v)) urls.add(v); }\n else if (v && typeof v === \"object\") Object.values(v).forEach(walk);\n };\n facts.forEach(walk);\n return urls;\n}\n\nexport function citationProblems(profile, facts) {\n const known = factUrls(facts);\n const problems = [];\n profile.split(\"\\n\").forEach((line, i) => {\n const urls = (line.match(URL_RE) ?? []).map(trimUrl);\n for (const u of urls) if (!known.has(u)) problems.push(`line ${i + 1}: ${u} is not in the facts`);\n if (/^\\s*(?:[-*]|\\d+\\.)\\s/.test(line) && !urls.length) problems.push(`line ${i + 1}: list item cites no permalink: ${line.trim().slice(0, 60)}`);\n });\n return problems;\n}\n\nif (import.meta.url === pathToFileURL(process.argv[1] ?? \"\").href) {\n const args = process.argv.slice(2);\n const after = (flag) => { const i = args.indexOf(flag); if (i < 0) return []; const out = []; for (let j = i + 1; j < args.length && !args[j].startsWith(\"--\"); j++) out.push(args[j]); return out; };\n const problems = [];\n const [profile] = after(\"--profile\");\n if (profile) problems.push(...citationProblems(readFileSync(profile, \"utf8\"), after(\"--facts\").map((f) => JSON.parse(readFileSync(f, \"utf8\")))));\n problems.forEach((p) => console.log(p));\n process.exit(problems.length ? 1 : 0);\n}\n"};
|
|
@@ -31,20 +31,20 @@ export const realRun = async (args) => (await exec("gh", args, { maxBuffer: 1 <<
|
|
|
31
31
|
const ATTEMPTS = 3;
|
|
32
32
|
|
|
33
33
|
async function search(run, q, withState = false) {
|
|
34
|
-
const args = ["api", "graphql", "--paginate", "--
|
|
34
|
+
const args = ["api", "graphql", "--paginate", "--jq", ".data.search", "-f", `query=${searchQuery(withState)}`, "-f", `q=${q}`];
|
|
35
35
|
let out;
|
|
36
36
|
for (let attempt = 1; ; attempt++) {
|
|
37
37
|
try {
|
|
38
38
|
out = await run(args);
|
|
39
39
|
break;
|
|
40
40
|
} catch (e) {
|
|
41
|
-
if (attempt === ATTEMPTS
|
|
41
|
+
if (attempt === ATTEMPTS) throw e;
|
|
42
42
|
await new Promise((r) => setTimeout(r, 1000 * attempt + Math.random() * 1000));
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
const pages = JSON.parse(
|
|
46
|
-
const nodes = pages.flatMap((p) => p.
|
|
47
|
-
return { total: pages[0].
|
|
45
|
+
const pages = out.split("\n").filter(Boolean).map((l) => JSON.parse(l));
|
|
46
|
+
const nodes = pages.flatMap((p) => p.nodes).filter((n) => n?.url);
|
|
47
|
+
return { total: pages[0].issueCount, nodes };
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
const item = (n) => ({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eve-hub",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.65",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Bot-side code shared by every hub agent: registry tools, the roster hook, the eve channel, the host sandbox and browser, and model helpers.",
|
|
6
6
|
"type": "module",
|
package/world.mjs
CHANGED
|
@@ -20178,14 +20178,14 @@ function eventStatements(threadId2, event, text, at, id = eventId(), only) {
|
|
|
20178
20178
|
}
|
|
20179
20179
|
|
|
20180
20180
|
// ../../hosted-hub/src/store/routing.ts
|
|
20181
|
-
var
|
|
20182
|
-
|
|
20183
|
-
|
|
20184
|
-
SELECT
|
|
20185
|
-
|
|
20186
|
-
|
|
20187
|
-
|
|
20188
|
-
|
|
20181
|
+
var PARTICIPANTS = (thread, except = "''") => `SELECT b.name AS bot FROM bots b, (SELECT ${thread} AS t, ${except} AS x) p WHERE MAX(
|
|
20182
|
+
COALESCE((SELECT created_at FROM sessions WHERE thread_id = p.t AND bot = b.name ORDER BY created_at DESC LIMIT 1), -2),
|
|
20183
|
+
COALESCE((SELECT created_at FROM messages WHERE thread_id = p.t AND author = b.name AND author_kind = 'agent' AND id IS NOT p.x ORDER BY created_at DESC LIMIT 1), -2),
|
|
20184
|
+
COALESCE((SELECT created_at FROM messages WHERE thread_id = p.t AND author_kind = 'event' AND json_extract(event, '$.agent') = +b.name AND json_extract(event, '$.kind') = 'join'
|
|
20185
|
+
AND id IS NOT p.x ORDER BY created_at DESC LIMIT 1), -2))
|
|
20186
|
+
> COALESCE((SELECT created_at FROM messages WHERE thread_id = p.t AND author_kind = 'event' AND json_extract(event, '$.agent') = +b.name AND json_extract(event, '$.kind') = 'leave'
|
|
20187
|
+
ORDER BY created_at DESC LIMIT 1), -1)`;
|
|
20188
|
+
var NOT_IN_BEFORE = `NOT EXISTS (SELECT 1 FROM (${PARTICIPANTS("?", "?")}) WHERE bot = ?)`;
|
|
20189
20189
|
var hasJoins = (threadId2) => !threadId2.startsWith("dm-") && !threadId2.startsWith("sch-");
|
|
20190
20190
|
function routingStatements(messageId2, threadId2, bots, now, { by, joins }, held = {}, steer = false) {
|
|
20191
20191
|
const quiet2 = !joins || !hasJoins(threadId2);
|
|
@@ -20198,7 +20198,7 @@ function routingStatements(messageId2, threadId2, bots, now, { by, joins }, held
|
|
|
20198
20198
|
`@${bot} joined${by !== bot ? ` (added by @${by})` : ""}`,
|
|
20199
20199
|
now,
|
|
20200
20200
|
eventId(),
|
|
20201
|
-
s(`${fresh} AND NOT EXISTS (SELECT 1 FROM (${PARTICIPANTS("?")}) WHERE bot = ?)`, messageId2,
|
|
20201
|
+
s(`${fresh} AND NOT EXISTS (SELECT 1 FROM (${PARTICIPANTS("?")}) WHERE bot = ?)`, messageId2, threadId2, bot)
|
|
20202
20202
|
),
|
|
20203
20203
|
s(`INSERT OR IGNORE INTO sessions (id,thread_id,bot,created_at) SELECT ?,?,?,? WHERE ${fresh}`, sessionId(), threadId2, bot, now, messageId2),
|
|
20204
20204
|
s(
|
|
@@ -20243,6 +20243,7 @@ var LEASE_LEFT = `SELECT l.setup_failed_until - ${NOW_MS} AS ms FROM local_runne
|
|
|
20243
20243
|
|
|
20244
20244
|
// ../../hosted-hub/src/store/schema.ts
|
|
20245
20245
|
var OPEN_TURN_STATUSES = "('streaming','cancelling')";
|
|
20246
|
+
var UNSETTLED_DELIVERIES = "unsettled AS MATERIALIZED (SELECT rowid AS rid, * FROM deliveries WHERE accepted_at IS NULL AND failed_at IS NULL AND revoked_at IS NULL)";
|
|
20246
20247
|
|
|
20247
20248
|
// mention-tokens.mjs
|
|
20248
20249
|
function codeRanges(text) {
|
|
@@ -27221,11 +27222,11 @@ var SESSION_WORK_DELIVERY = `busy.accepted_at IS NOT NULL AND busy.failed_at IS
|
|
|
27221
27222
|
WHERE td.delivery_id = busy.id AND t.status NOT IN ('streaming','cancelling'))`;
|
|
27222
27223
|
var SESSION_BUSY = `s.ended_at IS NULL AND EXISTS (SELECT 1 FROM deliveries busy
|
|
27223
27224
|
WHERE busy.session_id = s.id AND ${SESSION_WORK_DELIVERY})`;
|
|
27224
|
-
var PENDING_FROM = `SELECT d.id, d.message_id, d.session_id, d.bot, m.thread_id, t.title, m.author, m.author_kind, m.text, m.status, m.status_from, m.metadata, d.steer,
|
|
27225
|
+
var PENDING_FROM = `WITH ${UNSETTLED_DELIVERIES} SELECT d.id, d.message_id, d.session_id, d.bot, m.thread_id, t.title, m.author, m.author_kind, m.text, m.status, m.status_from, m.metadata, d.steer,
|
|
27225
27226
|
EXISTS (SELECT 1 FROM deliveries a WHERE a.session_id = d.session_id AND a.accepted_at IS NOT NULL) AS session_started,
|
|
27226
27227
|
s.ended_at IS NOT NULL AS session_ended, ${onLocal("d.bot")} AS on_local,
|
|
27227
27228
|
(SELECT json_group_array(json_object('id', a.upload_id, 'name', a.name, 'size', a.size)) FROM (SELECT * FROM message_attachments WHERE message_id = m.id ORDER BY position) a) AS attachments
|
|
27228
|
-
FROM
|
|
27229
|
+
FROM unsettled d JOIN messages m ON m.id = d.message_id JOIN threads t ON t.id = m.thread_id JOIN sessions s ON s.id = d.session_id`;
|
|
27229
27230
|
var UNSETTLED = `d.accepted_at IS NULL AND d.failed_at IS NULL AND d.revoked_at IS NULL AND d.held_at IS NULL AND NOT ${heldByHopLimit("m")}`;
|
|
27230
27231
|
var TASK_CARD = `(SELECT CASE WHEN json_array_length(json_extract(t.activity, '$.taskCard.tasks')) > 0 THEN json_extract(t.activity, '$.taskCard') END
|
|
27231
27232
|
FROM turns t WHERE t.id = m.turn_id AND m.author_kind = 'agent' AND t.status NOT IN ${OPEN_TURN_STATUSES}
|
|
@@ -27258,29 +27259,40 @@ function handedBack({ session, roster, asked = [], posted = [], notify }, text,
|
|
|
27258
27259
|
if ([text, ...posted].some((t) => mentionTokens(t).some((name) => name !== me && known.has(name)))) return [];
|
|
27259
27260
|
return [...new Set(asked.filter((a) => (a.linked || named.has(a.id)) && a.author !== session.bot && roster.includes(a.author) && mentionTokens(a.text).includes(me)).map((a) => a.author))];
|
|
27260
27261
|
}
|
|
27261
|
-
var FOLD_STATE_SQL = `SELECT
|
|
27262
|
-
|
|
27263
|
-
(SELECT d.message_id FROM
|
|
27264
|
-
(SELECT json_group_array(json_object('id', dd.id, 'author', m.author, 'text', m.text, 'linked', EXISTS (SELECT 1 FROM turn_deliveries td JOIN turns t ON t.id = td.turn_id WHERE td.delivery_id = dd.id AND t.status IN ${OPEN_TURN_STATUSES})))
|
|
27265
|
-
FROM deliveries dd JOIN messages m ON m.id = dd.message_id WHERE dd.session_id = s.id AND m.author_kind = 'agent'
|
|
27266
|
-
AND (EXISTS (SELECT 1 FROM turn_deliveries td JOIN turns t ON t.id = td.turn_id WHERE td.delivery_id = dd.id AND t.status IN ${OPEN_TURN_STATUSES}) OR NOT EXISTS (SELECT 1 FROM turn_deliveries td WHERE td.delivery_id = dd.id))) AS asked,
|
|
27267
|
-
(SELECT json_group_array(p.text) FROM messages p WHERE p.turn_id = (SELECT t.id FROM turns t WHERE t.session_id = s.id AND t.status IN ${OPEN_TURN_STATUSES} ORDER BY t.started_at DESC LIMIT 1)
|
|
27268
|
-
AND p.author = s.bot AND p.author_kind = 'agent' AND p.status IS NULL) AS posted,
|
|
27269
|
-
(SELECT p.id FROM messages p WHERE p.turn_id = (SELECT t.id FROM turns t WHERE t.session_id = s.id AND t.status IN ${OPEN_TURN_STATUSES} ORDER BY t.started_at DESC LIMIT 1)
|
|
27270
|
-
AND p.author = s.bot AND p.author_kind = 'agent' AND p.status IS NULL ORDER BY p.created_at DESC, p.rowid DESC LIMIT 1) AS last_post,
|
|
27262
|
+
var FOLD_STATE_SQL = `SELECT * FROM (WITH sess AS (SELECT * FROM sessions WHERE id = ?), ${UNSETTLED_DELIVERIES}
|
|
27263
|
+
SELECT s.id, s.thread_id, s.bot, COALESCE(f.folded_index, -1) AS folded_index, (SELECT json_group_array(name) FROM (SELECT name FROM bots WHERE ${inRoster} ORDER BY position)) AS roster,
|
|
27264
|
+
(SELECT d.message_id FROM unsettled d WHERE d.bot = s.bot AND d.held_at IS NOT NULL AND d.held_paused IS NOT 1 ORDER BY d.rid LIMIT 1) AS held,
|
|
27271
27265
|
(SELECT json_object('person', person, 'names', json(names)) FROM human_notify) AS notify,
|
|
27272
27266
|
(SELECT json_object('status', t.status, 'id', t.id, 'text', t.reply_text, 'lastStep', json(t.last_step), 'activity', json(t.activity),
|
|
27273
27267
|
'trigger', ${triggerOf("t")}) FROM turns t WHERE t.session_id = s.id AND t.status IN ${OPEN_TURN_STATUSES}
|
|
27274
27268
|
ORDER BY t.started_at DESC LIMIT 1) AS open_turn,
|
|
27275
27269
|
(SELECT json_object('to', c.report_to, 'trigger', c.report_trigger, 'before', c.report_before, 'title', (SELECT title FROM threads WHERE id = s.thread_id))
|
|
27276
27270
|
FROM compactions c WHERE c.session_id = s.id AND c.report_to IS NOT NULL) AS report
|
|
27277
|
-
FROM
|
|
27271
|
+
FROM sess s LEFT JOIN session_folds f ON f.session_id = s.id)`;
|
|
27272
|
+
var TURN_END_SQL = `SELECT * FROM (WITH sess AS (SELECT * FROM sessions WHERE id = ?),
|
|
27273
|
+
open_turn AS (SELECT t.id FROM turns t WHERE t.session_id = (SELECT id FROM sess) AND t.status IN ${OPEN_TURN_STATUSES} ORDER BY t.started_at DESC LIMIT 1),
|
|
27274
|
+
answered AS (SELECT delivery_id AS id FROM turn_deliveries WHERE turn_id = (SELECT id FROM open_turn) UNION SELECT value FROM json_each(?))
|
|
27275
|
+
SELECT (SELECT json_group_array(bot) FROM (${PARTICIPANTS("(SELECT thread_id FROM sess)")})) AS participants,
|
|
27276
|
+
(SELECT json_group_array(json_object('id', dd.id, 'author', m.author, 'text', m.text, 'linked', EXISTS (SELECT 1 FROM turn_deliveries td WHERE td.delivery_id = dd.id AND td.turn_id = (SELECT id FROM open_turn))))
|
|
27277
|
+
FROM answered a CROSS JOIN deliveries dd ON dd.id = a.id CROSS JOIN messages m ON m.id = dd.message_id WHERE dd.session_id = (SELECT id FROM sess) AND m.author_kind = 'agent'
|
|
27278
|
+
AND (EXISTS (SELECT 1 FROM turn_deliveries td WHERE td.delivery_id = dd.id AND td.turn_id = (SELECT id FROM open_turn)) OR NOT EXISTS (SELECT 1 FROM turn_deliveries td WHERE td.delivery_id = dd.id))) AS asked,
|
|
27279
|
+
(SELECT json_group_array(p.text) FROM messages p WHERE p.turn_id = (SELECT id FROM open_turn)
|
|
27280
|
+
AND p.author = (SELECT bot FROM sess) AND p.author_kind = 'agent' AND p.status IS NULL) AS posted,
|
|
27281
|
+
(SELECT p.id FROM messages p WHERE p.turn_id = (SELECT id FROM open_turn)
|
|
27282
|
+
AND p.author = (SELECT bot FROM sess) AND p.author_kind = 'agent' AND p.status IS NULL ORDER BY p.created_at DESC, p.rowid DESC LIMIT 1) AS last_post)`;
|
|
27283
|
+
var namedDeliveries = (chunks) => [...new Set(chunks.flatMap((c) => c.events.flatMap((e) => e.meta?.deliveryIds ?? [])))];
|
|
27284
|
+
var completes = (chunks) => chunks.some((c) => c.events.some((e) => e.type === "turn.completed"));
|
|
27285
|
+
async function readFoldState(get, sessionId2, chunks) {
|
|
27286
|
+
const row = await get(FOLD_STATE_SQL, sessionId2);
|
|
27287
|
+
if (!row) return void 0;
|
|
27288
|
+
return foldState({ ...row, ...completes(chunks) ? await get(TURN_END_SQL, sessionId2, JSON.stringify(namedDeliveries(chunks))) : {} });
|
|
27289
|
+
}
|
|
27278
27290
|
function foldState(row) {
|
|
27279
27291
|
if (!row) return void 0;
|
|
27280
27292
|
return {
|
|
27281
27293
|
session: { id: String(row.id), thread: String(row.thread_id), bot: String(row.bot) },
|
|
27282
27294
|
roster: JSON.parse(String(row.roster)),
|
|
27283
|
-
participants: JSON.parse(String(row.participants)),
|
|
27295
|
+
participants: JSON.parse(String(row.participants ?? "[]")),
|
|
27284
27296
|
held: row.held == null ? null : String(row.held),
|
|
27285
27297
|
asked: JSON.parse(String(row.asked ?? "[]")),
|
|
27286
27298
|
posted: JSON.parse(String(row.posted ?? "[]")),
|
|
@@ -27530,7 +27542,7 @@ function fold(state, chunks, now = Date.now()) {
|
|
|
27530
27542
|
return { statements: stmts, deliver };
|
|
27531
27543
|
}
|
|
27532
27544
|
async function foldInTx(tx, sessionId2, chunks) {
|
|
27533
|
-
const state =
|
|
27545
|
+
const state = await readFoldState((sql, ...args) => tx.get(sql, ...args), sessionId2, chunks);
|
|
27534
27546
|
const result = state ? fold(state, chunks) : foldSubagent(subagentState(sessionId2, await tx.get(SUBAGENT_STATE_SQL, sessionId2)), chunks);
|
|
27535
27547
|
for (const stmt of result.statements) await tx.write(stmt.sql, ...stmt.args);
|
|
27536
27548
|
return result.deliver;
|