toolroll 0.9.53 → 0.9.55
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 +17 -0
- package/README.md +5 -4
- package/dist/agent-onboard.js +4 -1
- package/dist/approval-rules-ui.d.ts +3 -2
- package/dist/approval-rules-ui.js +15 -20
- package/dist/assignment-ui.d.ts +3 -2
- package/dist/assignment-ui.js +4 -11
- package/dist/backup-ui.d.ts +2 -1
- package/dist/backup-ui.js +16 -27
- package/dist/brand-mark.d.ts +3 -2
- package/dist/brand-mark.js +4 -4
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +25 -25
- package/dist/browser-shell.d.ts +2 -1
- package/dist/browser-shell.js +9 -9
- package/dist/browser-workspace.d.ts +2 -3
- package/dist/browser-workspace.js +0 -3
- package/dist/chat-ask.js +8 -8
- package/dist/chat-core.d.ts +171 -0
- package/dist/chat-core.js +288 -0
- package/dist/chat-decide.d.ts +10 -8
- package/dist/chat-decide.js +26 -21
- package/dist/chat-delivery-state.d.ts +51 -75
- package/dist/chat-delivery-state.js +140 -170
- package/dist/chat-delivery.d.ts +21 -2
- package/dist/chat-delivery.js +313 -194
- package/dist/chat-flow.d.ts +29 -1
- package/dist/chat-flow.js +24 -50
- package/dist/chat-messages.d.ts +319 -0
- package/dist/chat-messages.js +841 -0
- package/dist/chat-migration.d.ts +30 -0
- package/dist/chat-migration.js +802 -0
- package/dist/chat-polish.d.ts +5 -4
- package/dist/chat-polish.js +9 -10
- package/dist/chat-quiet.d.ts +3 -0
- package/dist/chat-quiet.js +5 -0
- package/dist/check-levels-ui.d.ts +2 -1
- package/dist/check-levels-ui.js +26 -53
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +9 -3
- package/dist/coding-shipping-ui.d.ts +2 -1
- package/dist/coding-shipping-ui.js +6 -6
- package/dist/coding-ui.d.ts +3 -1
- package/dist/coding-ui.js +30 -29
- package/dist/contracts/chat-content.d.ts +71 -2
- package/dist/contracts/chat-content.js +22 -3
- package/dist/contracts/chat-tables.d.ts +11 -4
- package/dist/contracts/chat-tables.js +165 -87
- package/dist/contracts/cli.d.ts +1 -3
- package/dist/contracts/console-api.d.ts +1 -1
- package/dist/contracts/console-api.js +6 -5
- package/dist/contracts/discord-callback.js +2 -1
- package/dist/contracts/health.d.ts +1 -3
- package/dist/contracts/slack-callback.js +3 -2
- package/dist/contracts/store-json.d.ts +1 -5
- package/dist/contracts/store-json.js +1 -4
- package/dist/contracts/teams-callback.js +2 -1
- package/dist/control-ui.d.ts +5 -4
- package/dist/control-ui.js +9 -34
- package/dist/credentials-ui.d.ts +5 -4
- package/dist/credentials-ui.js +23 -24
- package/dist/demo-chat.d.ts +3 -3
- package/dist/demo-chat.js +29 -76
- package/dist/discord-chat.js +19 -38
- package/dist/discord-settings.d.ts +3 -2
- package/dist/discord-settings.js +21 -55
- package/dist/discord.js +4 -14
- package/dist/envelope.js +10 -0
- package/dist/evidence-pack.d.ts +2 -1
- package/dist/evidence-pack.js +45 -49
- package/dist/export-ui.d.ts +7 -2
- package/dist/export-ui.js +4 -10
- package/dist/export.js +4 -4
- package/dist/flow-gallery-ui.d.ts +4 -3
- package/dist/flow-gallery-ui.js +28 -43
- package/dist/flow-starters-ui.d.ts +2 -1
- package/dist/flow-starters-ui.js +12 -15
- package/dist/flow-triggers.d.ts +1 -0
- package/dist/flow-triggers.js +16 -14
- package/dist/flows-ui.d.ts +4 -3
- package/dist/flows-ui.js +19 -30
- package/dist/flows.js +2 -1
- package/dist/guides.js +108 -3
- package/dist/html.d.ts +76 -0
- package/dist/html.js +173 -0
- package/dist/integrations-ui.d.ts +2 -1
- package/dist/integrations-ui.js +19 -23
- package/dist/kits-ui.d.ts +5 -4
- package/dist/kits-ui.js +18 -20
- package/dist/knowledge-ui.d.ts +6 -5
- package/dist/knowledge-ui.js +26 -25
- package/dist/lead-context.d.ts +11 -1
- package/dist/lead-context.js +6 -8
- package/dist/ledger-view.d.ts +2 -1
- package/dist/ledger-view.js +27 -29
- package/dist/limits-ui.d.ts +2 -1
- package/dist/limits-ui.js +7 -7
- package/dist/live-bus.d.ts +50 -8
- package/dist/live-bus.js +133 -50
- package/dist/mate-tools.js +3 -2
- package/dist/mcp-oauth.js +35 -36
- package/dist/models-ui.d.ts +2 -1
- package/dist/models-ui.js +25 -35
- package/dist/modes.d.ts +13 -1
- package/dist/modes.js +17 -1
- package/dist/monitoring-ui.d.ts +4 -3
- package/dist/monitoring-ui.js +13 -26
- package/dist/notification-rows.d.ts +30 -0
- package/dist/notification-rows.js +102 -0
- package/dist/openrouter-models.d.ts +2 -1
- package/dist/openrouter-models.js +5 -9
- package/dist/operate.js +16 -9
- package/dist/people-audit-ui.d.ts +2 -1
- package/dist/people-audit-ui.js +13 -13
- package/dist/plane-review.js +5 -11
- package/dist/policy-ui.d.ts +3 -2
- package/dist/policy-ui.js +17 -23
- package/dist/project-delete-ui.d.ts +4 -3
- package/dist/project-delete-ui.js +15 -27
- package/dist/project-delete.js +21 -26
- package/dist/pull-request-ui.d.ts +2 -1
- package/dist/pull-request-ui.js +17 -32
- package/dist/recipe-ui.d.ts +8 -7
- package/dist/recipe-ui.js +39 -32
- package/dist/release-adapters.d.ts +60 -0
- package/dist/release-adapters.js +435 -0
- package/dist/release-cli.d.ts +24 -0
- package/dist/release-cli.js +181 -0
- package/dist/release-gate.d.ts +80 -0
- package/dist/release-gate.js +150 -0
- package/dist/release-links.d.ts +29 -0
- package/dist/release-links.js +102 -0
- package/dist/release.d.ts +331 -0
- package/dist/release.js +550 -0
- package/dist/reply-shape.d.ts +2 -1
- package/dist/reply-shape.js +5 -5
- package/dist/repository-context-ui.d.ts +2 -1
- package/dist/repository-context-ui.js +2 -7
- package/dist/request-budget-ui.d.ts +2 -1
- package/dist/request-budget-ui.js +16 -16
- package/dist/result-review.d.ts +2 -3
- package/dist/result-review.js +3 -9
- package/dist/result-shots.js +2 -2
- package/dist/retention-ui.d.ts +7 -1
- package/dist/retention-ui.js +7 -11
- package/dist/retention.js +1 -1
- package/dist/serve.d.ts +11 -2
- package/dist/serve.js +69 -58
- package/dist/server/chat.d.ts +3 -6
- package/dist/server/chat.js +969 -1006
- package/dist/server/chrome.d.ts +291 -0
- package/dist/server/chrome.js +747 -0
- package/dist/server/console.css +2819 -0
- package/dist/server/flows.d.ts +2 -6
- package/dist/server/flows.js +840 -766
- package/dist/server/guards.d.ts +1 -1
- package/dist/server/guards.js +5 -2
- package/dist/server/handler-context.d.ts +1 -1
- package/dist/server/handler-context.js +1 -1
- package/dist/server/handler-registry.d.ts +8 -6
- package/dist/server/handler-registry.js +14 -2
- package/dist/server/http.d.ts +274 -0
- package/dist/server/http.js +239 -0
- package/dist/server/live.d.ts +34 -0
- package/dist/server/live.js +309 -0
- package/dist/server/pages.d.ts +2 -6
- package/dist/server/pages.js +808 -789
- package/dist/server/people-tokens.d.ts +1 -6
- package/dist/server/people-tokens.js +580 -572
- package/dist/server/remote.d.ts +4 -7
- package/dist/server/remote.js +37 -70
- package/dist/server/render-chat.d.ts +402 -0
- package/dist/server/render-chat.js +927 -0
- package/dist/server/render-pages.d.ts +535 -0
- package/dist/server/render-pages.js +1277 -0
- package/dist/server/render-people.d.ts +32 -0
- package/dist/server/render-people.js +121 -0
- package/dist/server/render-results.d.ts +744 -0
- package/dist/server/render-results.js +2237 -0
- package/dist/server/render-settings.d.ts +147 -0
- package/dist/server/render-settings.js +577 -0
- package/dist/server/render-tasks.d.ts +632 -0
- package/dist/server/render-tasks.js +2345 -0
- package/dist/server/request-context.d.ts +46 -0
- package/dist/server/request-context.js +17 -0
- package/dist/server/route-table.d.ts +25 -3
- package/dist/server/route-table.js +163 -75
- package/dist/server/runtime.d.ts +15 -7
- package/dist/server/runtime.js +7 -1
- package/dist/server/session.d.ts +138 -0
- package/dist/server/session.js +94 -0
- package/dist/server/settings.d.ts +1 -5
- package/dist/server/settings.js +2182 -2117
- package/dist/server/task-views.d.ts +11 -4
- package/dist/server/task-views.js +26 -22
- package/dist/server/tasks.d.ts +9 -9
- package/dist/server/tasks.js +1486 -1418
- package/dist/server-telemetry.d.ts +4 -3
- package/dist/server-telemetry.js +4 -8
- package/dist/skills-ui.d.ts +4 -3
- package/dist/skills-ui.js +14 -17
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +2 -1
- package/dist/slack-api.d.ts +5 -1
- package/dist/slack-api.js +2 -2
- package/dist/slack-chat.d.ts +9 -4
- package/dist/slack-chat.js +29 -52
- package/dist/slack-settings.d.ts +3 -2
- package/dist/slack-settings.js +23 -57
- package/dist/slack.js +6 -16
- package/dist/spend-ui.d.ts +2 -1
- package/dist/spend-ui.js +9 -32
- package/dist/sso-ui.d.ts +3 -2
- package/dist/sso-ui.js +26 -27
- package/dist/storage-ui.d.ts +2 -1
- package/dist/storage-ui.js +14 -33
- package/dist/store.d.ts +97 -364
- package/dist/store.js +207 -1531
- package/dist/surface.js +14 -3
- package/dist/task-composer.d.ts +2 -1
- package/dist/task-composer.js +7 -7
- package/dist/task-status.d.ts +4 -3
- package/dist/task-status.js +15 -16
- package/dist/team-http.d.ts +5 -9
- package/dist/team-http.js +5 -63
- package/dist/team-ui.d.ts +2 -1
- package/dist/team-ui.js +2 -5
- package/dist/teammate-question.js +12 -12
- package/dist/teammates-ui.d.ts +5 -4
- package/dist/teammates-ui.js +61 -90
- package/dist/teams-chat.js +12 -9
- package/dist/teams-settings.d.ts +3 -2
- package/dist/teams-settings.js +22 -33
- package/dist/teams.js +4 -5
- package/dist/telegram-flow.js +5 -5
- package/dist/telegram-settings.d.ts +3 -2
- package/dist/telegram-settings.js +14 -25
- package/dist/telegram.js +4 -4
- package/dist/toolroll-update-ui.d.ts +3 -2
- package/dist/toolroll-update-ui.js +35 -39
- package/dist/toolroll-update.d.ts +10 -0
- package/dist/toolroll-update.js +50 -3
- package/dist/tools-ui.d.ts +5 -2
- package/dist/tools-ui.js +31 -31
- package/dist/when-html.d.ts +11 -3
- package/dist/when-html.js +5 -6
- package/dist/workspace-revision.js +3 -1
- package/dist/workspace-ui.d.ts +4 -2
- package/dist/workspace-ui.js +10 -10
- package/package.json +1 -1
- package/dist/server/shared.d.ts +0 -3151
- package/dist/server/shared.js +0 -12001
- package/dist/slack-state.d.ts +0 -13
- package/dist/slack-state.js +0 -10
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { createLiveRooms, LiveConnection } from "../live-bus.js";
|
|
2
|
+
import { reproveRemote } from "../operate-remote.js";
|
|
3
|
+
import { rowVisible } from "../project.js";
|
|
4
|
+
import { TEAM_PASSWORD_REVERIFY_MS } from "../team-http.js";
|
|
5
|
+
import { handlersOf } from "./handler-registry.js";
|
|
6
|
+
import { respond } from "./http.js";
|
|
7
|
+
import { adapterPolicy } from "./route-policy.js";
|
|
8
|
+
import { requestContext } from "./request-context.js";
|
|
9
|
+
/** A page asks for a few rooms at once: its workspace, what it shows, its conversation. */
|
|
10
|
+
export const LIVE_ROOMS_PER_PAGE = 5;
|
|
11
|
+
const ROOM_NAME = /^(workspace|task|flow|chat|team)(?::([^?]{1,400}))?(?:\?([^#]{0,600}))?$/;
|
|
12
|
+
/** At most one workspace nudge a second: a burst of writes is one re-read, the first heard at once. */
|
|
13
|
+
const WORKSPACE_SETTLE_MS = 1_000;
|
|
14
|
+
/** A running reply's preview: at most one snapshot every 80 ms; the last one always goes out. */
|
|
15
|
+
const TURN_SETTLE_MS = 80;
|
|
16
|
+
/** A room's name, read strictly: anything else is not a room. */
|
|
17
|
+
export function parseLiveRoom(name) {
|
|
18
|
+
const match = ROOM_NAME.exec(name);
|
|
19
|
+
if (match === null)
|
|
20
|
+
return null;
|
|
21
|
+
const [, kind, id, query] = match;
|
|
22
|
+
let key = null;
|
|
23
|
+
try {
|
|
24
|
+
key = id === undefined ? null : decodeURIComponent(id);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const params = new URLSearchParams(query ?? "");
|
|
30
|
+
const only = (...allowed) => [...params.keys()].every(one => allowed.includes(one)) && allowed.every(one => params.getAll(one).length <= 1);
|
|
31
|
+
switch (kind) {
|
|
32
|
+
case "workspace": return key === null && query === undefined ? { kind } : null;
|
|
33
|
+
case "task": return key !== null && key !== "" && query === undefined ? { kind, task: key } : null;
|
|
34
|
+
case "flow": {
|
|
35
|
+
const flow = Number(key), card = params.get("card");
|
|
36
|
+
if (key === null || !/^[1-9][0-9]{0,9}$/.test(key) || !only("card", "editing") || (card !== null && !/^[1-9][0-9]{0,9}$/.test(card)))
|
|
37
|
+
return null;
|
|
38
|
+
return { kind, flow, card: card === null ? null : Number(card), editing: params.get("editing") === "1" };
|
|
39
|
+
}
|
|
40
|
+
case "chat":
|
|
41
|
+
if (key !== null || !only("task", "project") || (params.has("task") && params.has("project")))
|
|
42
|
+
return null;
|
|
43
|
+
return { kind, task: params.get("task") || null, project: params.get("project") || null };
|
|
44
|
+
case "team":
|
|
45
|
+
if (key !== null || !only("conversation"))
|
|
46
|
+
return null;
|
|
47
|
+
return { kind, conversation: params.get("conversation") || null };
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const json = (response, status, value) => respond(response, status, "application/json", JSON.stringify(value));
|
|
52
|
+
export function createLiveHandlers(runtime, live) {
|
|
53
|
+
const { store, identify, liveCeiling, familyOf, taskRooms, flowRooms, clock, matePrincipal, taskChatFocus, chatScopeOf, liveTurns, team, visible } = runtime;
|
|
54
|
+
// The reader's workspace: one room for everyone, nudged by any meaningful write or a clock-bound lapse.
|
|
55
|
+
const workspaceRooms = createLiveRooms(() => `${live.workspaceRevision.current()}:${live.workspaceRevision.expiresAt(clock())}`, null, { bus: live.bus, minIntervalMs: WORKSPACE_SETTLE_MS });
|
|
56
|
+
// A team audience is per actor (and per token): its key carries who reads, the room keeps how to read as them.
|
|
57
|
+
const teamActors = new Map();
|
|
58
|
+
const teamRooms = createLiveRooms(key => {
|
|
59
|
+
const actor = teamActors.get(key);
|
|
60
|
+
const cursor = actor === undefined ? null : team.cursor(actor, JSON.parse(key)[3] || undefined);
|
|
61
|
+
return cursor === null ? null : String(cursor);
|
|
62
|
+
}, null, { bus: live.bus });
|
|
63
|
+
const chatViewers = new Set();
|
|
64
|
+
/** The same session, still signed in, from the cookie and the account (never this request's context). */
|
|
65
|
+
const readBrowser = (request, who) => {
|
|
66
|
+
const again = identify(request, false);
|
|
67
|
+
if (again?.via !== "cookie" || again.name !== who.name || again.session.generation !== who.session.generation)
|
|
68
|
+
return null;
|
|
69
|
+
// The in-memory session may still exist after another process ends the saved session.
|
|
70
|
+
const hash = runtime.sessions.hashOf(again.session);
|
|
71
|
+
const saved = hash === null ? null : store.webSession(hash), account = store.accountOf(again.name);
|
|
72
|
+
if (saved === null || account === null || account.revokedAt !== null || account.generation !== again.session.generation
|
|
73
|
+
|| saved.generation !== account.generation || saved.role !== account.role || again.role !== account.role)
|
|
74
|
+
return null;
|
|
75
|
+
return again;
|
|
76
|
+
};
|
|
77
|
+
const sameBrowser = (request, who) => () => readBrowser(request, who) !== null;
|
|
78
|
+
function admitted(viewer, thread) {
|
|
79
|
+
let valid = false;
|
|
80
|
+
try {
|
|
81
|
+
valid = viewer.valid(thread);
|
|
82
|
+
}
|
|
83
|
+
catch { /* Failed proof stops this room. */ }
|
|
84
|
+
if (!valid) {
|
|
85
|
+
viewer.connection.send(viewer.room, "gone");
|
|
86
|
+
viewer.leave();
|
|
87
|
+
}
|
|
88
|
+
return valid;
|
|
89
|
+
}
|
|
90
|
+
/** A running reply for one viewer's thread: snapshots while it runs, then the last one. */
|
|
91
|
+
function follow(viewer, thread) {
|
|
92
|
+
if (!admitted(viewer, thread))
|
|
93
|
+
return;
|
|
94
|
+
const turn = liveTurns.get(thread);
|
|
95
|
+
if (turn === undefined || turn.done || viewer.detach !== null)
|
|
96
|
+
return;
|
|
97
|
+
let queued = null;
|
|
98
|
+
const snapshot = () => {
|
|
99
|
+
if (admitted(viewer, thread))
|
|
100
|
+
viewer.connection.send(viewer.room, "turn", { steps: turn.steps, done: turn.done, ok: turn.ok });
|
|
101
|
+
};
|
|
102
|
+
const flush = () => {
|
|
103
|
+
queued = null;
|
|
104
|
+
snapshot();
|
|
105
|
+
if (turn.done)
|
|
106
|
+
stop();
|
|
107
|
+
};
|
|
108
|
+
const listener = () => {
|
|
109
|
+
if (turn.done) {
|
|
110
|
+
if (queued !== null)
|
|
111
|
+
clearTimeout(queued);
|
|
112
|
+
flush();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (queued === null)
|
|
116
|
+
queued = setTimeout(flush, TURN_SETTLE_MS);
|
|
117
|
+
};
|
|
118
|
+
const stop = () => { if (queued !== null)
|
|
119
|
+
clearTimeout(queued); turn.listeners.delete(listener); viewer.detach = null; };
|
|
120
|
+
turn.listeners.add(listener);
|
|
121
|
+
viewer.detach = stop;
|
|
122
|
+
snapshot();
|
|
123
|
+
}
|
|
124
|
+
/** A reply starts on a thread: whoever has that conversation open follows it. */
|
|
125
|
+
function turnStarted(thread) {
|
|
126
|
+
for (const viewer of chatViewers) {
|
|
127
|
+
if (admitted(viewer) && store.liveMateThreadFor(viewer.name, viewer.scope)?.id === thread)
|
|
128
|
+
follow(viewer, thread);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** The actor a team stream reads as, and how it is proved again while open. */
|
|
132
|
+
function teamActorOf(request, who) {
|
|
133
|
+
const account = store.accountOf(who.name);
|
|
134
|
+
if (account === null || account.revokedAt !== null)
|
|
135
|
+
return null;
|
|
136
|
+
if (who.via === "cookie") {
|
|
137
|
+
const actor = { name: who.name, generation: who.session.generation };
|
|
138
|
+
return { actor, key: "", valid: sameBrowser(request, who) };
|
|
139
|
+
}
|
|
140
|
+
const principal = who.principal;
|
|
141
|
+
if (principal !== undefined) {
|
|
142
|
+
// An API token is re-proved against the store every time: unrevoked, unexpired, inside its project limit.
|
|
143
|
+
const actor = { name: principal.account, generation: principal.generation, principal };
|
|
144
|
+
return { actor, key: principal.tokenId, valid: () => {
|
|
145
|
+
const again = reproveRemote(store, principal, clock());
|
|
146
|
+
if (!again.ok)
|
|
147
|
+
return false;
|
|
148
|
+
actor.principal = { ...principal, scope: again.scope };
|
|
149
|
+
return true;
|
|
150
|
+
} };
|
|
151
|
+
}
|
|
152
|
+
// A password bearer is proved again once TEAM_PASSWORD_REVERIFY_MS has passed since it last was.
|
|
153
|
+
const actor = { name: who.name, generation: who.generation };
|
|
154
|
+
let provedAt = clock().getTime();
|
|
155
|
+
return { actor, key: "password", valid: () => {
|
|
156
|
+
const current = store.accountOf(actor.name);
|
|
157
|
+
if (!current || current.revokedAt !== null || current.generation !== actor.generation)
|
|
158
|
+
return false;
|
|
159
|
+
const now = clock().getTime();
|
|
160
|
+
if (now - provedAt < TEAM_PASSWORD_REVERIFY_MS)
|
|
161
|
+
return true;
|
|
162
|
+
const again = identify(request, false);
|
|
163
|
+
if (again?.via !== "bearer" || again.name !== actor.name || again.generation !== actor.generation)
|
|
164
|
+
return false;
|
|
165
|
+
provedAt = now;
|
|
166
|
+
return true;
|
|
167
|
+
} };
|
|
168
|
+
}
|
|
169
|
+
async function liveStream(ctx) {
|
|
170
|
+
const { url, who, request, response } = ctx;
|
|
171
|
+
const now = clock();
|
|
172
|
+
if (who === null)
|
|
173
|
+
return json(response, 401, { error: "session" });
|
|
174
|
+
if (!adapterPolicy({ caller: who.via, capability: who.via === "bearer" && who.principal !== undefined ? who.principal.scope : who.role === "approver" ? "act" : "read" }).ok)
|
|
175
|
+
return json(response, 403, { error: "session" });
|
|
176
|
+
const names = url.searchParams.getAll("room");
|
|
177
|
+
if ([...url.searchParams.keys()].some(key => key !== "room") || names.length === 0 || names.length > LIVE_ROOMS_PER_PAGE || new Set(names).size !== names.length)
|
|
178
|
+
return json(response, 400, { error: "room" });
|
|
179
|
+
const rooms = names.map(name => ({ name, room: parseLiveRoom(name) }));
|
|
180
|
+
if (rooms.some(one => one.room === null) || rooms.filter(one => one.room?.kind === "workspace").length > 1 || rooms.filter(one => one.room?.kind === "chat").length > 1 || rooms.filter(one => one.room?.kind === "team").length > 1)
|
|
181
|
+
return json(response, 400, { error: "room" });
|
|
182
|
+
// Admit every room before the stream opens, with the answers each page's own stream gave.
|
|
183
|
+
const joins = [];
|
|
184
|
+
for (const { name, room } of rooms) {
|
|
185
|
+
if (room.kind !== "team" && who.via !== "cookie")
|
|
186
|
+
return json(response, 403, { error: "session" });
|
|
187
|
+
if (room.kind === "workspace") {
|
|
188
|
+
const browser = who;
|
|
189
|
+
joins.push(connection => workspaceRooms.join("workspace", { name: who.name, room: name, connection, valid: sameBrowser(request, browser) }));
|
|
190
|
+
}
|
|
191
|
+
else if (room.kind === "task") {
|
|
192
|
+
const browser = who;
|
|
193
|
+
const family = familyOf(room.task);
|
|
194
|
+
if (family === null || family.problem !== null)
|
|
195
|
+
return json(response, 404, { error: "task" });
|
|
196
|
+
const repo = family.root.repo, root = family.root.id, signedIn = sameBrowser(request, browser);
|
|
197
|
+
joins.push(connection => taskRooms.join(root, { name: who.name, room: name, connection,
|
|
198
|
+
// A sign-out or a narrowed account stops it.
|
|
199
|
+
valid: () => signedIn() && rowVisible(liveCeiling(), repo) && store.accountCanAccess(who.name, repo) }));
|
|
200
|
+
}
|
|
201
|
+
else if (room.kind === "flow") {
|
|
202
|
+
const browser = who;
|
|
203
|
+
const flow = store.getFlow(room.flow);
|
|
204
|
+
if (flow === null || flow.state !== "active" || !visible(flow.repo))
|
|
205
|
+
return json(response, 404, { error: "flow" });
|
|
206
|
+
const repo = flow.repo, signedIn = sameBrowser(request, browser);
|
|
207
|
+
joins.push(connection => flowRooms.join(flow.id, { name: who.name, room: name, connection, card: room.card, editing: room.editing,
|
|
208
|
+
valid: () => signedIn() && rowVisible(liveCeiling(), repo) && store.accountCanAccess(who.name, repo) }));
|
|
209
|
+
}
|
|
210
|
+
else if (room.kind === "chat") {
|
|
211
|
+
const browser = who;
|
|
212
|
+
if (who.role !== "approver")
|
|
213
|
+
return json(response, 403, { error: "session" });
|
|
214
|
+
if (matePrincipal(browser) === null)
|
|
215
|
+
return json(response, 403, { error: "standing" });
|
|
216
|
+
const focusTask = room.task === null ? null : taskChatFocus(room.task, now, who, { mintNonce: false });
|
|
217
|
+
if (room.task !== null && focusTask === null)
|
|
218
|
+
return json(response, 404, { error: "task" });
|
|
219
|
+
const chatProject = focusTask !== null ? null : live.chatProjectOf(room.project === null ? [] : [room.project]);
|
|
220
|
+
if (chatProject === undefined)
|
|
221
|
+
return json(response, 404, { error: "project" });
|
|
222
|
+
const scope = chatScopeOf(focusTask, chatProject);
|
|
223
|
+
const valid = (expectedThread) => requestContext.run({ actor: who.name, csrf: "", returnTo: "/chat" }, () => {
|
|
224
|
+
const again = readBrowser(request, browser);
|
|
225
|
+
if (again === null || again.role !== "approver")
|
|
226
|
+
return false;
|
|
227
|
+
const principal = matePrincipal(again);
|
|
228
|
+
if (principal === null)
|
|
229
|
+
return false;
|
|
230
|
+
const thread = store.liveMateThreadFor(again.name, scope);
|
|
231
|
+
// Even the default lead room is bound to the projects admitted when its thread opened.
|
|
232
|
+
if (thread !== null && thread.ceilingDigest !== principal.ceilingDigest)
|
|
233
|
+
return false;
|
|
234
|
+
if (expectedThread !== undefined && thread?.id !== expectedThread)
|
|
235
|
+
return false;
|
|
236
|
+
if (scope.kind === "task") {
|
|
237
|
+
const focus = taskChatFocus(room.task, clock(), again, { mintNonce: false });
|
|
238
|
+
return focus !== null && focus.id === scope.key;
|
|
239
|
+
}
|
|
240
|
+
return scope.kind !== "project" || live.chatProjectOf([scope.key]) === scope.key;
|
|
241
|
+
});
|
|
242
|
+
joins.push(connection => {
|
|
243
|
+
const viewer = { name: who.name, room: name, connection, scope, valid, detach: null, leave: () => {
|
|
244
|
+
viewer.detach?.();
|
|
245
|
+
chatViewers.delete(viewer);
|
|
246
|
+
unregister();
|
|
247
|
+
unprove();
|
|
248
|
+
if (connection.rooms === 0)
|
|
249
|
+
connection.close();
|
|
250
|
+
} };
|
|
251
|
+
chatViewers.add(viewer);
|
|
252
|
+
const unregister = connection.onClose(() => { viewer.detach?.(); chatViewers.delete(viewer); });
|
|
253
|
+
const unprove = connection.onKeepAlive(() => { admitted(viewer); });
|
|
254
|
+
const thread = store.liveMateThreadFor(who.name, scope);
|
|
255
|
+
if (thread !== null)
|
|
256
|
+
follow(viewer, thread.id);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
const proved = teamActorOf(request, who);
|
|
261
|
+
if (proved === null)
|
|
262
|
+
return json(response, 401, { version: 1, ok: false, code: "unauthenticated", message: "Sign in to continue." });
|
|
263
|
+
let initial;
|
|
264
|
+
try {
|
|
265
|
+
initial = await team.execute(proved.actor, { operation: room.conversation ? "show" : "list", args: room.conversation ? { conversationId: room.conversation } : {} });
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
return json(response, 503, { version: 1, ok: false, code: "unavailable", message: "Conversation updates are unavailable." });
|
|
269
|
+
}
|
|
270
|
+
if (!initial.ok || !initial.snapshot)
|
|
271
|
+
return json(response, 403, initial);
|
|
272
|
+
const key = JSON.stringify([proved.actor.name, proved.actor.generation, proved.key, room.conversation ?? ""]);
|
|
273
|
+
teamActors.set(key, proved.actor);
|
|
274
|
+
// The token or session proves itself again, then reads the room as it now stands: a narrowed project limit or a
|
|
275
|
+
// lost membership is a lost room. The room's cursor reads as the latest proof.
|
|
276
|
+
const valid = () => {
|
|
277
|
+
if (!proved.valid())
|
|
278
|
+
return false;
|
|
279
|
+
teamActors.set(key, proved.actor);
|
|
280
|
+
return team.cursor(proved.actor, room.conversation ?? undefined) !== null;
|
|
281
|
+
};
|
|
282
|
+
joins.push(connection => teamRooms.join(key, { name: who.name, room: name, connection, valid }));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
response.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-store", "x-content-type-options": "nosniff", "x-accel-buffering": "no" });
|
|
286
|
+
response.flushHeaders();
|
|
287
|
+
const connection = new LiveConnection(response);
|
|
288
|
+
// Each join below adds its room before the next speaks; a chat room alone keeps the stream open too.
|
|
289
|
+
for (const join of joins)
|
|
290
|
+
if (connection.open)
|
|
291
|
+
join(connection);
|
|
292
|
+
request.once("close", () => connection.close());
|
|
293
|
+
}
|
|
294
|
+
const detachChat = live.bus.subscribe(() => {
|
|
295
|
+
for (const viewer of [...chatViewers])
|
|
296
|
+
admitted(viewer);
|
|
297
|
+
});
|
|
298
|
+
return {
|
|
299
|
+
registrations: handlersOf("live", {}, { live: liveStream }),
|
|
300
|
+
turnStarted,
|
|
301
|
+
close() {
|
|
302
|
+
detachChat();
|
|
303
|
+
workspaceRooms.close();
|
|
304
|
+
teamRooms.close();
|
|
305
|
+
for (const viewer of [...chatViewers])
|
|
306
|
+
viewer.connection.close();
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
}
|
package/dist/server/pages.d.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import type { Registration } from './handler-registry.js';
|
|
2
1
|
import { type ServerResponse } from "node:http";
|
|
3
|
-
import type { HandlerContext } from './handler-context.js';
|
|
4
2
|
import type { ServerRuntime } from './runtime.js';
|
|
5
|
-
import { type Who } from "./
|
|
3
|
+
import { type Who } from "./session.js";
|
|
6
4
|
export declare function createPagesHandlers(runtime: ServerRuntime): {
|
|
7
|
-
registrations: Registration[];
|
|
8
|
-
get: (ctx: HandlerContext) => Promise<void>;
|
|
9
|
-
post: (ctx: HandlerContext) => Promise<void>;
|
|
5
|
+
registrations: import("./handler-registry.js").Registration[];
|
|
10
6
|
projectsScreen: (response: ServerResponse, who: Who, problem: string | null, status: number, returnTo?: string) => Promise<void>;
|
|
11
7
|
};
|