@desplega.ai/agent-swarm 2.3.0 → 2.4.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/README.md +1 -0
- package/dist/{acp-adapter-v85a2bqy.js → acp-adapter-kedstxx6.js} +6 -6
- package/dist/{actions-zgcxfspj.js → actions-vget6r1k.js} +18 -16
- package/dist/{amp-adapter-jw9xfz42.js → amp-adapter-xeb5mktv.js} +2 -2
- package/dist/{app-4e1s4675.js → app-mqap8t8m.js} +3 -3
- package/dist/{assistant-v238n9dk.js → assistant-h0gv97mv.js} +20 -18
- package/dist/{boot-reembed-p666cw5n.js → boot-reembed-qr8a894z.js} +1 -1
- package/dist/{boot-reembed-w6pm64j4.js → boot-reembed-rj85jjhw.js} +1 -1
- package/dist/{boot-scrub-sweep-jcyjagn3.js → boot-scrub-sweep-eyfvn6v9.js} +3 -3
- package/dist/{boot-seeds-gbedmamp.js → boot-seeds-xs9a8dx1.js} +4 -4
- package/dist/{claude-adapter-q7wspzt8.js → claude-adapter-bm23cye7.js} +5 -5
- package/dist/{claude-managed-adapter-n7ahjyx6.js → claude-managed-adapter-1mwrsm9d.js} +2 -2
- package/dist/{claude-managed-setup-wza784fb.js → claude-managed-setup-gkfgks4j.js} +1 -1
- package/dist/{claude-sdk-session-yf400ptr.js → claude-sdk-session-apb4ypz3.js} +5 -5
- package/dist/cli-02vhhssk.js +848 -0
- package/dist/{cli-4ync7m60.js → cli-0ebey9hg.js} +1 -1
- package/dist/{cli-665j3feb.js → cli-0xk505e8.js} +1 -1
- package/dist/{cli-30m5q8mc.js → cli-1cevz46c.js} +5 -5
- package/dist/{cli-gzvb3d4a.js → cli-1jx0srjt.js} +3 -3
- package/dist/{cli-zj4d6afs.js → cli-1pa3rz69.js} +21 -6
- package/dist/{cli-fhvqt2fp.js → cli-2rwet3r7.js} +1 -1
- package/dist/{cli-azs9bhc5.js → cli-3wjs22x3.js} +2 -2
- package/dist/{cli-dxwh4j32.js → cli-4fhyq711.js} +45 -36
- package/dist/{cli-yv3n83x8.js → cli-5ff2sjyt.js} +1 -1
- package/dist/{cli-6vcyw6v7.js → cli-5s5hx57y.js} +5 -3
- package/dist/{cli-hz64bcnj.js → cli-6ftrrwm1.js} +9 -371
- package/dist/{cli-zes4tsbb.js → cli-8b9wvjps.js} +1 -1
- package/dist/cli-8cxkw1f5.js +382 -0
- package/dist/{cli-4s1kqrqg.js → cli-8rwvksbc.js} +2 -2
- package/dist/{cli-e09hj50q.js → cli-8wkb09q5.js} +11 -11
- package/dist/{cli-yv0vgscc.js → cli-a1esmmee.js} +13 -2
- package/dist/{cli-4hmaheb6.js → cli-avmqhatn.js} +7 -7
- package/dist/{cli-g6p7c2my.js → cli-b80a1100.js} +1 -1
- package/dist/{cli-tr24eeny.js → cli-bfc3k1x5.js} +3 -3
- package/dist/{cli-ka01m5rb.js → cli-bhwa8rzc.js} +13 -11
- package/dist/{cli-8cvvytad.js → cli-bn26xbcs.js} +2 -2
- package/dist/{cli-k28p6xh7.js → cli-c8fwpgqb.js} +1 -1
- package/dist/{cli-rc3r0bnh.js → cli-ccn0eybf.js} +1 -1
- package/dist/{cli-9td5ss65.js → cli-dsgj3e7a.js} +2 -2
- package/dist/{cli-a17tzmn0.js → cli-ecshf504.js} +2 -2
- package/dist/{cli-d43xqzbe.js → cli-eqgfs202.js} +1 -1
- package/dist/{cli-ppapy34w.js → cli-f3nx1h8z.js} +2 -2
- package/dist/{cli-ea7chd26.js → cli-ffbrkh1r.js} +74 -35
- package/dist/{cli-xgn4rwyv.js → cli-kk2r80n5.js} +4 -4
- package/dist/{cli-hn3pftj2.js → cli-kz116kvw.js} +3 -3
- package/dist/{cli-1v8w782f.js → cli-mvg2hf8p.js} +4 -4
- package/dist/{cli-5d1m1qca.js → cli-mz1ezzzh.js} +1 -1
- package/dist/{cli-jhmjwqcg.js → cli-nme7wery.js} +6 -6
- package/dist/{cli-65q70v2v.js → cli-nrrg0fvy.js} +18 -4
- package/dist/{cli-8jtrekpy.js → cli-p1m2b6gc.js} +7 -7
- package/dist/{cli-re4wkbzx.js → cli-rg7ntypm.js} +328 -47
- package/dist/{cli-fp4xbw07.js → cli-rsd7ga7v.js} +10 -8
- package/dist/{cli-etvn5ej2.js → cli-sppc0cmw.js} +1 -1
- package/dist/{cli-qrwp40st.js → cli-tg9ksbn8.js} +1 -1
- package/dist/{cli-6p7ddg72.js → cli-tgvpmnwz.js} +2 -2
- package/dist/{cli-q1812jbw.js → cli-tj4j2ts4.js} +6 -6
- package/dist/{cli-kqypty34.js → cli-ttaz6c7m.js} +2 -2
- package/dist/{cli-w7z94e6c.js → cli-tx00c0h6.js} +4 -2
- package/dist/{cli-4g629sgr.js → cli-wrv09v7q.js} +20 -1
- package/dist/{cli-cby329ny.js → cli-wyrr38xm.js} +2 -2
- package/dist/{cli-y3dvz6ge.js → cli-x1p5dhq2.js} +3 -3
- package/dist/{cli-8qsx817b.js → cli-x719j3vs.js} +219 -629
- package/dist/{cli-9e9kjqsj.js → cli-xfe63zx3.js} +1 -1
- package/dist/{cli-wtg6edvd.js → cli-ywyrffff.js} +1 -1
- package/dist/{cli-0rh20bp3.js → cli-zvze9rbm.js} +1 -1
- package/dist/cli.js +14 -14
- package/dist/{codex-adapter-nhjj2veb.js → codex-adapter-pp2gq8gr.js} +3 -3
- package/dist/{codex-hook-x6tm6y5m.js → codex-hook-qwzccd64.js} +1 -1
- package/dist/{codex-session-runner-6hggrjbx.js → codex-session-runner-0vzn09t8.js} +3 -3
- package/dist/{commands-mrw1ph2w.js → commands-psqhkttb.js} +2 -2
- package/dist/{controller-pt1zam5w.js → controller-t20vz2k8.js} +2 -2
- package/dist/{controllers-connect-ymw5541d.js → controllers-connect-gkxmqw9d.js} +24 -20
- package/dist/{cursor-adapter-cf4zh5x0.js → cursor-adapter-3v542qe5.js} +1 -1
- package/dist/{db-hy80219q.js → db-2xbyenjr.js} +2 -2
- package/dist/{db-hhpy6m4x.js → db-nc1rd869.js} +3 -2
- package/dist/{dispatcher-6pdjtkr1.js → dispatcher-ewceb8ts.js} +17 -15
- package/dist/{dsh-adapter-fbx06re2.js → dsh-adapter-75xwh78t.js} +1 -1
- package/dist/{easter-egg-3d-6hg2r942.js → easter-egg-3d-xsc47jwf.js} +1 -1
- package/dist/{execute-bcqc69a5.js → execute-gzmw1mnw.js} +1 -1
- package/dist/{external-channel-j6vb6ya1.js → external-channel-a5dsmxn4.js} +2 -2
- package/dist/{google-generative-ai-phpmwxvg.js → google-generative-ai-bmq8gkf3.js} +1 -1
- package/dist/{google-vertex-es9ybgdy.js → google-vertex-b1rvp52s.js} +1 -1
- package/dist/{grok-adapter-1193sprp.js → grok-adapter-vj88xr23.js} +7 -7
- package/dist/handlers-zbs90bny.js +39 -0
- package/dist/{heartbeat-h1w1ktce.js → heartbeat-t2m9r3wa.js} +20 -18
- package/dist/{hook-skrbja18.js → hook-8femx1bg.js} +4 -4
- package/dist/{hook-ejja4bpe.js → hook-xa677f8r.js} +5 -5
- package/dist/{http-mzh41mfj.js → http-xpmmq028.js} +868 -253
- package/dist/{index-vjn3wbzf.js → index-8pwc1jc8.js} +17 -15
- package/dist/{index-epr2gnbw.js → index-cdzyfrsp.js} +1 -1
- package/dist/{index-xmmkyx82.js → index-dg2tmj99.js} +17 -15
- package/dist/index-nxa6me0q.js +41 -0
- package/dist/{index-6rmnn89d.js → index-s7ckdajq.js} +9 -9
- package/dist/{index-wgsw9xcz.js → index-syztqdt6.js} +1 -1
- package/dist/{index-x0q2a4f9.js → index-ysd45kgc.js} +1 -1
- package/dist/{index-g909y089.js → index-zgvqzjsw.js} +8 -8
- package/dist/{keepalive-4djvbm2j.js → keepalive-3nr4st3r.js} +3 -3
- package/dist/{lead-61kqp604.js → lead-td8h2sfn.js} +33 -31
- package/dist/{maintenance-f3xhnjsm.js → maintenance-verwpe2c.js} +1 -1
- package/dist/{oauth-refresh-sweep-fpd6x6rw.js → oauth-refresh-sweep-9q2t0y3q.js} +2 -2
- package/dist/{onboard-196tjgyn.js → onboard-aytqedmq.js} +2 -2
- package/dist/{opencode-adapter-eeesgfqk.js → opencode-adapter-f4hryjny.js} +4 -4
- package/dist/{otel-impl-g8fh4c21.js → otel-impl-hckw4wt1.js} +1 -1
- package/dist/{pi-mono-adapter-zasp3ze3.js → pi-mono-adapter-hxby0ce1.js} +6 -6
- package/dist/{pricing-refresh-sdcf5yaz.js → pricing-refresh-5nvme3sm.js} +3 -3
- package/dist/{rbac-roles-bsp5657v.js → rbac-roles-pd8c26m1.js} +5 -3
- package/dist/{rbac-roles-2x4g1wb5.js → rbac-roles-vbpy21mg.js} +2 -2
- package/dist/{render-v2-0pyb39bb.js → render-v2-7nqgvk2q.js} +3 -3
- package/dist/{resume-a86tkj6r.js → resume-b6djcj3c.js} +6 -6
- package/dist/{runtime-ar8310ba.js → runtime-txrxc9za.js} +1 -1
- package/dist/session-access-fc3f9y12.js +25 -0
- package/dist/{setup-ypbzcapd.js → setup-n39szm6b.js} +1 -1
- package/dist/{virtual-modules-wzk6jf3a.js → virtual-modules-wyp2k6z3.js} +2 -2
- package/dist/{work-objects-yjp8de12.js → work-objects-jem2fre0.js} +20 -18
- package/dist/{worker-4ptnn1ge.js → worker-wr6xgfr2.js} +33 -31
- package/openapi.json +844 -110
- package/package.json +1 -1
- package/src/apps/action-caller.ts +6 -0
- package/src/apps/script-types.ts +16 -3
- package/src/be/db/active-sessions.ts +5 -0
- package/src/be/db/session-visibility.ts +29 -0
- package/src/be/db/tasks/read.ts +61 -3
- package/src/be/db/tasks/write.ts +15 -6
- package/src/be/db.ts +92 -33
- package/src/be/events.ts +11 -0
- package/src/be/migrations/230_session_shares.sql +23 -0
- package/src/be/rbac-roles.ts +2 -0
- package/src/be/session-shares.ts +244 -0
- package/src/be/swarm-config-guard.ts +23 -2
- package/src/commands/runner.ts +11 -0
- package/src/http/active-sessions.ts +13 -2
- package/src/http/agents.ts +10 -3
- package/src/http/all-routes.ts +1 -0
- package/src/http/api-keys.ts +3 -1
- package/src/http/approval-requests.ts +2 -1
- package/src/http/apps.ts +36 -11
- package/src/http/config.ts +3 -0
- package/src/http/context.ts +2 -0
- package/src/http/core.ts +39 -2
- package/src/http/events.ts +6 -2
- package/src/http/favorite-owner.ts +1 -1
- package/src/http/fs.ts +38 -3
- package/src/http/index.ts +3 -1
- package/src/http/memory.ts +3 -1
- package/src/http/ranking.ts +367 -0
- package/src/http/realtime.ts +26 -21
- package/src/http/route-def.ts +77 -11
- package/src/http/script-runs.ts +2 -1
- package/src/http/scripts.ts +8 -0
- package/src/http/session-access.ts +179 -0
- package/src/http/session-data.ts +10 -2
- package/src/http/sessions.ts +212 -12
- package/src/http/stats.ts +7 -3
- package/src/http/tasks.ts +83 -7
- package/src/http/used-by.ts +11 -3
- package/src/prompts/session-templates.ts +3 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/ranking/telemetry-ranking.ts +147 -0
- package/src/ranking/usage.ts +57 -0
- package/src/rbac/admission.ts +22 -7
- package/src/rbac/legacy-policy.ts +2 -0
- package/src/rbac/permissions.ts +9 -0
- package/src/realtime/auth.ts +69 -0
- package/src/realtime/tickets.ts +4 -1
- package/src/realtime/transport.ts +77 -9
- package/src/slack/blocks.ts +18 -0
- package/src/slack/dm.ts +29 -0
- package/src/telemetry-contract/EVENTS.md +3 -0
- package/src/telemetry-contract/SOURCE.json +37 -4
- package/src/telemetry-contract/catalog.json +69 -0
- package/src/telemetry-contract/ranking/fixtures/expected.json +30 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/bad-category.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/bad-claim.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/blank-description.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/blank-name.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/description-too-long.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/domain-uppercase.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/domain-with-scheme.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/domain-with-www.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/http-source-url.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/missing-description.json +10 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/missing-version.json +10 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/name-not-string.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/name-too-long.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/not-object.json +1 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/null.json +1 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/other-with-subcategory.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/oversize.json +1 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/short-claim.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/subcategory-other-category.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/trailing-data.json +1 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/unknown-field.json +12 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/unsupported-version.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/updated-at-not-date.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/updated-at-offset.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/wrong-case-field.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/valid/category-without-subcategory.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/valid/full.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/valid/minimal.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/valid/padded-text.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/valid/unicode-name.json +11 -0
- package/src/telemetry-contract/ranking/listing.v1.schema.json +61 -0
- package/src/telemetry-contract/ranking/taxonomy.json +8 -0
- package/src/telemetry-contract/ranking-taxonomy.gen.ts +11 -0
- package/src/telemetry-contract/types.gen.ts +708 -1
- package/src/telemetry.ts +26 -2
- package/src/tests/apps-rbac.test.ts +189 -2
- package/src/tests/apps-script-types.test.ts +16 -3
- package/src/tests/check-pr-body.test.ts +23 -0
- package/src/tests/check-ranking-entry.test.ts +146 -0
- package/src/tests/db-extraction-contract.test.ts +1 -0
- package/src/tests/db-task-read-contract.test.ts +1 -0
- package/src/tests/docs-index-footprint.test.js +2 -2
- package/src/tests/org-identity.test.ts +44 -0
- package/src/tests/ranking-listing.test.ts +112 -0
- package/src/tests/ranking-route.test.ts +319 -0
- package/src/tests/ranking-usage.test.ts +50 -0
- package/src/tests/rbac-admission-e2e.test.ts +8 -0
- package/src/tests/rbac-admission.test.ts +38 -0
- package/src/tests/rbac-charact-http.test.ts +4 -3
- package/src/tests/rbac-engine.test.ts +2 -0
- package/src/tests/rbac-roles.test.ts +2 -0
- package/src/tests/realtime-room-surfaces.test.ts +3 -1
- package/src/tests/realtime-session-join-race.test.ts +142 -0
- package/src/tests/realtime-tickets.test.ts +24 -17
- package/src/tests/realtime-transport.test.ts +91 -0
- package/src/tests/session-route-posture.test.ts +639 -0
- package/src/tests/session-sharing-routes.test.ts +282 -0
- package/src/tests/session-sharing.test.ts +526 -0
- package/src/tests/sessions-detail-batch.test.ts +7 -1
- package/src/tests/slack-tool-blocks.test.ts +101 -0
- package/src/tests/slack-update.test.ts +55 -0
- package/src/tests/slack-user-dm.test.ts +177 -0
- package/src/tests/used-by-route.test.ts +10 -0
- package/src/tools/slack-post.ts +18 -12
- package/src/tools/slack-read.ts +13 -1
- package/src/tools/slack-reply.ts +7 -10
- package/src/tools/slack-start-thread.ts +18 -11
- package/src/tools/slack-update.ts +2 -9
- package/src/types.ts +35 -0
- package/src/utils/org-identity.ts +33 -0
- package/templates/skills/apps/SKILL.md +17 -0
- package/templates/skills/apps/content.md +17 -0
- package/dist/handlers-tb3xgjnm.js +0 -37
- package/dist/index-nyfcdpky.js +0 -39
- package/dist/{cli-n5mrsg0f.js → cli-60cgh1rv.js} +3 -3
- /package/dist/{cli-j0v75y13.js → cli-y83zk5sb.js} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@desplega.ai/agent-swarm",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "agent-swarm.dev — an open-source operating system for AI work: a lead agent breaks goals into tasks, routes them to specialized workers such as Claude Code or Codex, runs each worker in an isolated container, and preserves shared memory, tools, schedules, and review gates so delegated work compounds across sessions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-swarm.dev",
|
package/src/apps/script-types.ts
CHANGED
|
@@ -8,6 +8,13 @@ const APP_TYPES_PREAMBLE = `// ── Swarm Apps: generated per-app types (sourc
|
|
|
8
8
|
// Rows are the app's declared columns plus the 5 system columns. Query
|
|
9
9
|
// overloads narrow only when appId AND query are string literals.
|
|
10
10
|
|
|
11
|
+
export type AppActionCaller = {
|
|
12
|
+
kind: "user" | "agent" | "operator" | "guest";
|
|
13
|
+
id: string | null;
|
|
14
|
+
name?: string;
|
|
15
|
+
pageId?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
11
18
|
export interface SwarmAppQueryResult<Row> {
|
|
12
19
|
success: boolean;
|
|
13
20
|
status: number;
|
|
@@ -175,9 +182,8 @@ function renderQuery(
|
|
|
175
182
|
}
|
|
176
183
|
|
|
177
184
|
function renderApp(app: AppRecord, namespace: string): string {
|
|
178
|
-
// Reserve
|
|
179
|
-
|
|
180
|
-
const modelNames = new Set<string>(["ActionName"]);
|
|
185
|
+
// Reserve action types so model names cannot collide with them.
|
|
186
|
+
const modelNames = new Set<string>(["ActionName", "ActionArgs", "AppActionCaller"]);
|
|
181
187
|
const renderedModels = Object.entries(app.definition.models).map(([modelName, model]) =>
|
|
182
188
|
renderModel(modelName, model, modelNames),
|
|
183
189
|
);
|
|
@@ -202,6 +208,13 @@ ${renderedModels.map((model) => model.source).join("\n\n")}
|
|
|
202
208
|
|
|
203
209
|
/** Declared actions. Invocation is REST-only: POST /api/apps/<id>/actions/<name>. */
|
|
204
210
|
export type ActionName = ${actionType};
|
|
211
|
+
|
|
212
|
+
/** Script action arguments include server-stamped app and caller context. */
|
|
213
|
+
export interface ActionArgs {
|
|
214
|
+
app: { id: ${JSON.stringify(app.id)} };
|
|
215
|
+
caller: AppActionCaller;
|
|
216
|
+
[key: string]: unknown;
|
|
217
|
+
}
|
|
205
218
|
}
|
|
206
219
|
|
|
207
220
|
export interface SwarmSdk {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ActiveSession } from "../../types";
|
|
1
2
|
import { getDbClient } from "./runtime";
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -28,3 +29,7 @@ export async function refreshActiveSessionOnActivity(
|
|
|
28
29
|
);
|
|
29
30
|
return result.changes > 0;
|
|
30
31
|
}
|
|
32
|
+
|
|
33
|
+
export async function getActiveSessionById(id: string): Promise<ActiveSession | null> {
|
|
34
|
+
return getDbClient().get<ActiveSession>("SELECT * FROM active_sessions WHERE id = ?", [id]);
|
|
35
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-list visibility for session sharing. `all`: sessions the viewer
|
|
3
|
+
* owns, ownerless sessions, and sessions shared with the viewer or everyone.
|
|
4
|
+
* `mine`: owned by the viewer. `shared`: owned by someone else and shared with
|
|
5
|
+
* the viewer or everyone. A null `userId` (a guest page session) owns nothing.
|
|
6
|
+
*/
|
|
7
|
+
export type SessionViewer = { userId: string | null; scope: "all" | "mine" | "shared" };
|
|
8
|
+
|
|
9
|
+
/** SQL condition + params restricting root tasks (alias `r`) to what `viewer` may open. */
|
|
10
|
+
export function sessionViewerCondition(viewer: SessionViewer): {
|
|
11
|
+
sql: string;
|
|
12
|
+
params: (string | null)[];
|
|
13
|
+
} {
|
|
14
|
+
const shared = `EXISTS (SELECT 1 FROM session_shares s
|
|
15
|
+
WHERE s.rootTaskId = r.id AND s.revokedAt IS NULL AND (s.userId IS NULL OR s.userId = ?))`;
|
|
16
|
+
if (viewer.scope === "mine") {
|
|
17
|
+
return { sql: "r.requestedByUserId = ?", params: [viewer.userId] };
|
|
18
|
+
}
|
|
19
|
+
if (viewer.scope === "shared") {
|
|
20
|
+
return {
|
|
21
|
+
sql: `(r.requestedByUserId IS NOT NULL AND r.requestedByUserId IS NOT ? AND ${shared})`,
|
|
22
|
+
params: [viewer.userId, viewer.userId],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
sql: `(r.requestedByUserId IS NULL OR r.requestedByUserId = ? OR ${shared})`,
|
|
27
|
+
params: [viewer.userId, viewer.userId],
|
|
28
|
+
};
|
|
29
|
+
}
|
package/src/be/db/tasks/read.ts
CHANGED
|
@@ -556,10 +556,14 @@ export async function hasNonTerminalRerouteDecisionChild(parentId: string): Prom
|
|
|
556
556
|
return row !== undefined && row !== null;
|
|
557
557
|
}
|
|
558
558
|
|
|
559
|
-
export async function getTasksByAgentId(
|
|
559
|
+
export async function getTasksByAgentId(
|
|
560
|
+
agentId: string,
|
|
561
|
+
readableBy?: { userId: string | null },
|
|
562
|
+
): Promise<AgentTask[]> {
|
|
563
|
+
const readable = readableBy ? readableSessionCondition(readableBy.userId) : undefined;
|
|
560
564
|
const rows = await getDbClient().query<AgentTaskRow>(
|
|
561
|
-
|
|
562
|
-
[agentId],
|
|
565
|
+
`SELECT * FROM agent_tasks WHERE agentId = ?${readable ? ` AND ${readable.sql}` : ""} ORDER BY createdAt DESC`,
|
|
566
|
+
[agentId, ...(readable?.params ?? [])],
|
|
563
567
|
);
|
|
564
568
|
return rows.map(rowToAgentTask);
|
|
565
569
|
}
|
|
@@ -642,6 +646,48 @@ export interface TaskFilters {
|
|
|
642
646
|
limit?: number;
|
|
643
647
|
offset?: number;
|
|
644
648
|
includeHeartbeat?: boolean;
|
|
649
|
+
/** Only tasks of sessions this user may read; `userId: null` is a guest. */
|
|
650
|
+
readableBy?: { userId: string | null };
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* The SQL twin of `sessionAccessForRoot` (../../session-shares). A session's
|
|
655
|
+
* root is its topmost existing task; the session is hidden when the root has
|
|
656
|
+
* an owner other than the user and no active share names the user or
|
|
657
|
+
* everyone. Descending from hidden roots is linear in their tasks at any chain
|
|
658
|
+
* depth, and as a WHERE term it keeps paging and `COUNT(*)` on the same rows.
|
|
659
|
+
* `parentTaskId` is fixed at insert to an existing task, so no parent cycle
|
|
660
|
+
* (which would have no root) can form.
|
|
661
|
+
*
|
|
662
|
+
* `taskIdColumn` applies the same filter to any table keyed by task id. A
|
|
663
|
+
* nullable column keeps its task-less rows: they belong to no session.
|
|
664
|
+
*/
|
|
665
|
+
export function readableSessionCondition(
|
|
666
|
+
userId: string | null,
|
|
667
|
+
taskIdColumn = "agent_tasks.id",
|
|
668
|
+
opts: { nullable?: boolean } = {},
|
|
669
|
+
): { sql: string; params: string[] } {
|
|
670
|
+
const otherOwner = userId
|
|
671
|
+
? "r.requestedByUserId IS NOT NULL AND r.requestedByUserId != ?"
|
|
672
|
+
: "r.requestedByUserId IS NOT NULL";
|
|
673
|
+
const shareMatch = userId ? "(s.userId IS NULL OR s.userId = ?)" : "s.userId IS NULL";
|
|
674
|
+
const notHidden = `${taskIdColumn} NOT IN (
|
|
675
|
+
WITH RECURSIVE hidden(id) AS (
|
|
676
|
+
SELECT r.id FROM agent_tasks r
|
|
677
|
+
WHERE (r.parentTaskId IS NULL
|
|
678
|
+
OR NOT EXISTS (SELECT 1 FROM agent_tasks p WHERE p.id = r.parentTaskId))
|
|
679
|
+
AND ${otherOwner}
|
|
680
|
+
AND NOT EXISTS (SELECT 1 FROM session_shares s
|
|
681
|
+
WHERE s.rootTaskId = r.id AND s.revokedAt IS NULL AND ${shareMatch})
|
|
682
|
+
UNION ALL
|
|
683
|
+
SELECT c.id FROM agent_tasks c JOIN hidden ON c.parentTaskId = hidden.id
|
|
684
|
+
)
|
|
685
|
+
SELECT id FROM hidden
|
|
686
|
+
)`;
|
|
687
|
+
return {
|
|
688
|
+
sql: opts.nullable ? `(${taskIdColumn} IS NULL OR ${notHidden})` : notHidden,
|
|
689
|
+
params: userId ? [userId, userId] : [],
|
|
690
|
+
};
|
|
645
691
|
}
|
|
646
692
|
|
|
647
693
|
export function getAllTasks(filters?: TaskFilters): Promise<AgentTask[]>;
|
|
@@ -756,6 +802,12 @@ export async function getAllTasks(
|
|
|
756
802
|
);
|
|
757
803
|
}
|
|
758
804
|
|
|
805
|
+
if (filters?.readableBy) {
|
|
806
|
+
const readable = readableSessionCondition(filters.readableBy.userId);
|
|
807
|
+
conditions.push(readable.sql);
|
|
808
|
+
params.push(...readable.params);
|
|
809
|
+
}
|
|
810
|
+
|
|
759
811
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
760
812
|
const limit = filters?.limit ?? 25;
|
|
761
813
|
const offset = filters?.offset ?? 0;
|
|
@@ -916,6 +968,12 @@ export async function getTasksCount(
|
|
|
916
968
|
);
|
|
917
969
|
}
|
|
918
970
|
|
|
971
|
+
if (filters?.readableBy) {
|
|
972
|
+
const readable = readableSessionCondition(filters.readableBy.userId);
|
|
973
|
+
conditions.push(readable.sql);
|
|
974
|
+
params.push(...readable.params);
|
|
975
|
+
}
|
|
976
|
+
|
|
919
977
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
920
978
|
const query = `SELECT COUNT(*) as count FROM agent_tasks ${whereClause}`;
|
|
921
979
|
|
package/src/be/db/tasks/write.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
type AgentTaskRow,
|
|
28
28
|
getTaskById,
|
|
29
29
|
NEVER_STARTED_TASK_STATUSES,
|
|
30
|
+
readableSessionCondition,
|
|
30
31
|
rowToAgentTask,
|
|
31
32
|
} from "./read";
|
|
32
33
|
|
|
@@ -1019,12 +1020,16 @@ export async function resumeTask(taskId: string): Promise<AgentTask | null> {
|
|
|
1019
1020
|
* Used on startup to resume tasks that were interrupted by deployment.
|
|
1020
1021
|
* Returns tasks ordered by creation time (oldest first for FIFO).
|
|
1021
1022
|
*/
|
|
1022
|
-
export async function getPausedTasksForAgent(
|
|
1023
|
+
export async function getPausedTasksForAgent(
|
|
1024
|
+
agentId: string,
|
|
1025
|
+
readableBy?: { userId: string | null },
|
|
1026
|
+
): Promise<AgentTask[]> {
|
|
1027
|
+
const readable = readableBy ? readableSessionCondition(readableBy.userId) : undefined;
|
|
1023
1028
|
const rows = await getDbClient().query<AgentTaskRow>(
|
|
1024
1029
|
`SELECT * FROM agent_tasks
|
|
1025
|
-
WHERE agentId = ? AND status = 'paused'
|
|
1030
|
+
WHERE agentId = ? AND status = 'paused'${readable ? ` AND ${readable.sql}` : ""}
|
|
1026
1031
|
ORDER BY createdAt ASC, rowid ASC`,
|
|
1027
|
-
[agentId],
|
|
1032
|
+
[agentId, ...(readable?.params ?? [])],
|
|
1028
1033
|
);
|
|
1029
1034
|
return rows.map(rowToAgentTask);
|
|
1030
1035
|
}
|
|
@@ -1093,15 +1098,19 @@ export async function resetOrphanedInProgressTasksForAgent(
|
|
|
1093
1098
|
* Used by hooks to detect task cancellation and stop the worker loop.
|
|
1094
1099
|
* Returns tasks cancelled within the last 5 minutes.
|
|
1095
1100
|
*/
|
|
1096
|
-
export async function getRecentlyCancelledTasksForAgent(
|
|
1101
|
+
export async function getRecentlyCancelledTasksForAgent(
|
|
1102
|
+
agentId: string,
|
|
1103
|
+
readableBy?: { userId: string | null },
|
|
1104
|
+
): Promise<AgentTask[]> {
|
|
1097
1105
|
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000).toISOString();
|
|
1106
|
+
const readable = readableBy ? readableSessionCondition(readableBy.userId) : undefined;
|
|
1098
1107
|
const rows = await getDbClient().query<AgentTaskRow>(
|
|
1099
1108
|
`SELECT * FROM agent_tasks
|
|
1100
1109
|
WHERE agentId = ?
|
|
1101
1110
|
AND status = 'cancelled'
|
|
1102
|
-
AND finishedAt > ?
|
|
1111
|
+
AND finishedAt > ?${readable ? ` AND ${readable.sql}` : ""}
|
|
1103
1112
|
ORDER BY finishedAt DESC`,
|
|
1104
|
-
[agentId, fiveMinutesAgo],
|
|
1113
|
+
[agentId, fiveMinutesAgo, ...(readable?.params ?? [])],
|
|
1105
1114
|
);
|
|
1106
1115
|
return rows.map(rowToAgentTask);
|
|
1107
1116
|
}
|
package/src/be/db.ts
CHANGED
|
@@ -195,6 +195,7 @@ import {
|
|
|
195
195
|
} from "./db/context-versions";
|
|
196
196
|
import { clearAgentModelProfileOnOverrideChange } from "./db/model-profiles";
|
|
197
197
|
import { getDb, getDbClient } from "./db/runtime";
|
|
198
|
+
import { type SessionViewer, sessionViewerCondition } from "./db/session-visibility";
|
|
198
199
|
import { classifyTaskHumanFree, reclassifyTaskHumanFree } from "./db/tasks/human-free";
|
|
199
200
|
import {
|
|
200
201
|
type AgentTaskRow,
|
|
@@ -203,6 +204,7 @@ import {
|
|
|
203
204
|
findExistingLinearTrackerContextWork,
|
|
204
205
|
getTaskById,
|
|
205
206
|
getTasksByAgentId,
|
|
207
|
+
readableSessionCondition,
|
|
206
208
|
rowToAgentTask,
|
|
207
209
|
rowToAgentTaskSummary,
|
|
208
210
|
} from "./db/tasks/read";
|
|
@@ -2081,13 +2083,17 @@ export async function getPendingSteeringForTask(taskId: string): Promise<Steerin
|
|
|
2081
2083
|
return await getSteeringMessagesForTask(taskId, { status: "pending" });
|
|
2082
2084
|
}
|
|
2083
2085
|
|
|
2084
|
-
export async function getPendingSteeringForAgent(
|
|
2086
|
+
export async function getPendingSteeringForAgent(
|
|
2087
|
+
agentId: string,
|
|
2088
|
+
readableBy?: { userId: string | null },
|
|
2089
|
+
): Promise<SteeringMessage[]> {
|
|
2090
|
+
const readable = readableBy ? readableSessionCondition(readableBy.userId, "t.id") : undefined;
|
|
2085
2091
|
const rows = await getDbClient().query<TaskSteeringMessageRow>(
|
|
2086
2092
|
`${STEERING_MESSAGE_SELECT}
|
|
2087
2093
|
JOIN agent_tasks t ON t.id = m.task_id
|
|
2088
|
-
WHERE t.agentId = ? AND m.status = 'pending'
|
|
2094
|
+
WHERE t.agentId = ? AND m.status = 'pending'${readable ? ` AND ${readable.sql}` : ""}
|
|
2089
2095
|
ORDER BY m.created_at ASC, m.rowid ASC`,
|
|
2090
|
-
[agentId],
|
|
2096
|
+
[agentId, ...(readable?.params ?? [])],
|
|
2091
2097
|
);
|
|
2092
2098
|
return rows.map(rowToSteeringMessage);
|
|
2093
2099
|
}
|
|
@@ -2185,27 +2191,33 @@ export async function hasPendingSteering(taskId: string): Promise<boolean> {
|
|
|
2185
2191
|
// Combined Queries (Agent with Tasks)
|
|
2186
2192
|
// ============================================================================
|
|
2187
2193
|
|
|
2188
|
-
export async function getAgentWithTasks(
|
|
2194
|
+
export async function getAgentWithTasks(
|
|
2195
|
+
id: string,
|
|
2196
|
+
readableBy?: { userId: string | null },
|
|
2197
|
+
): Promise<AgentWithTasks | null> {
|
|
2189
2198
|
return await getDbClient().transaction(
|
|
2190
2199
|
async () => {
|
|
2191
2200
|
const agent = await getAgentById(id);
|
|
2192
2201
|
if (!agent) return null;
|
|
2193
2202
|
|
|
2194
|
-
const tasks = await getTasksByAgentId(id);
|
|
2203
|
+
const tasks = await getTasksByAgentId(id, readableBy);
|
|
2195
2204
|
return { ...agent, tasks };
|
|
2196
2205
|
},
|
|
2197
2206
|
{ readOnly: true },
|
|
2198
2207
|
);
|
|
2199
2208
|
}
|
|
2200
2209
|
|
|
2201
|
-
export async function getAllAgentsWithTasks(opts?: {
|
|
2210
|
+
export async function getAllAgentsWithTasks(opts?: {
|
|
2211
|
+
slim?: boolean;
|
|
2212
|
+
readableBy?: { userId: string | null };
|
|
2213
|
+
}): Promise<AgentWithTasks[]> {
|
|
2202
2214
|
return await getDbClient().transaction(
|
|
2203
2215
|
async () => {
|
|
2204
2216
|
const agents = await getAllAgents({ slim: opts?.slim ?? false });
|
|
2205
2217
|
return await Promise.all(
|
|
2206
2218
|
agents.map(async (agent) => ({
|
|
2207
2219
|
...agent,
|
|
2208
|
-
tasks: await getTasksByAgentId(agent.id),
|
|
2220
|
+
tasks: await getTasksByAgentId(agent.id, opts?.readableBy),
|
|
2209
2221
|
})),
|
|
2210
2222
|
);
|
|
2211
2223
|
},
|
|
@@ -2268,10 +2280,16 @@ export async function createLogEntry(entry: {
|
|
|
2268
2280
|
return rowToAgentLog(row);
|
|
2269
2281
|
}
|
|
2270
2282
|
|
|
2271
|
-
export async function getLogsByAgentId(
|
|
2283
|
+
export async function getLogsByAgentId(
|
|
2284
|
+
agentId: string,
|
|
2285
|
+
readableBy?: { userId: string | null },
|
|
2286
|
+
): Promise<AgentLog[]> {
|
|
2287
|
+
const readable = readableBy
|
|
2288
|
+
? readableSessionCondition(readableBy.userId, "agent_log.taskId", { nullable: true })
|
|
2289
|
+
: undefined;
|
|
2272
2290
|
const rows = await getDbClient().query<AgentLogRow>(
|
|
2273
|
-
|
|
2274
|
-
[agentId],
|
|
2291
|
+
`SELECT * FROM agent_log WHERE agentId = ?${readable ? ` AND ${readable.sql}` : ""} ORDER BY createdAt DESC`,
|
|
2292
|
+
[agentId, ...(readable?.params ?? [])],
|
|
2275
2293
|
);
|
|
2276
2294
|
return rows.map(rowToAgentLog);
|
|
2277
2295
|
}
|
|
@@ -2315,16 +2333,24 @@ export async function getLogsByEventType(eventType: AgentLogEventType): Promise<
|
|
|
2315
2333
|
return rows.map(rowToAgentLog);
|
|
2316
2334
|
}
|
|
2317
2335
|
|
|
2318
|
-
export async function getAllLogs(
|
|
2336
|
+
export async function getAllLogs(
|
|
2337
|
+
limit?: number,
|
|
2338
|
+
readableBy?: { userId: string | null },
|
|
2339
|
+
): Promise<AgentLog[]> {
|
|
2340
|
+
const readable = readableBy
|
|
2341
|
+
? readableSessionCondition(readableBy.userId, "agent_log.taskId", { nullable: true })
|
|
2342
|
+
: undefined;
|
|
2343
|
+
const readableParams = readable?.params ?? [];
|
|
2319
2344
|
if (limit) {
|
|
2320
2345
|
const rows = await getDbClient().query<AgentLogRow>(
|
|
2321
|
-
|
|
2322
|
-
[limit],
|
|
2346
|
+
`SELECT * FROM agent_log WHERE eventType != 'agent_status_change'${readable ? ` AND ${readable.sql}` : ""} ORDER BY createdAt DESC LIMIT ?`,
|
|
2347
|
+
[...readableParams, limit],
|
|
2323
2348
|
);
|
|
2324
2349
|
return rows.map(rowToAgentLog);
|
|
2325
2350
|
}
|
|
2326
2351
|
const rows = await getDbClient().query<AgentLogRow>(
|
|
2327
|
-
|
|
2352
|
+
`SELECT * FROM agent_log${readable ? ` WHERE ${readable.sql}` : ""} ORDER BY createdAt DESC`,
|
|
2353
|
+
readableParams,
|
|
2328
2354
|
);
|
|
2329
2355
|
return rows.map(rowToAgentLog);
|
|
2330
2356
|
}
|
|
@@ -5820,8 +5846,13 @@ export interface ConcurrentContext {
|
|
|
5820
5846
|
* Returns processing inbox messages, recent task delegations by leads,
|
|
5821
5847
|
* and currently active (in-progress) tasks across the swarm.
|
|
5822
5848
|
*/
|
|
5823
|
-
export async function getConcurrentContext(
|
|
5849
|
+
export async function getConcurrentContext(readableBy?: {
|
|
5850
|
+
userId: string | null;
|
|
5851
|
+
}): Promise<ConcurrentContext> {
|
|
5824
5852
|
const client = getDbClient();
|
|
5853
|
+
const readable = readableBy ? readableSessionCondition(readableBy.userId, "t.id") : undefined;
|
|
5854
|
+
const readableSql = readable ? ` AND ${readable.sql}` : "";
|
|
5855
|
+
const readableParams = readable?.params ?? [];
|
|
5825
5856
|
|
|
5826
5857
|
// 1. Inbox messages currently being processed (status = 'processing')
|
|
5827
5858
|
const processingInboxMessages = await client.query<{
|
|
@@ -5850,9 +5881,9 @@ export async function getConcurrentContext(): Promise<ConcurrentContext> {
|
|
|
5850
5881
|
FROM agent_tasks t
|
|
5851
5882
|
LEFT JOIN agents a ON t.agentId = a.id
|
|
5852
5883
|
WHERE t.createdAt > ?
|
|
5853
|
-
AND t.creatorAgentId IN (SELECT id FROM agents WHERE isLead = 1)
|
|
5884
|
+
AND t.creatorAgentId IN (SELECT id FROM agents WHERE isLead = 1)${readableSql}
|
|
5854
5885
|
ORDER BY t.createdAt DESC`,
|
|
5855
|
-
[fiveMinutesAgo],
|
|
5886
|
+
[fiveMinutesAgo, ...readableParams],
|
|
5856
5887
|
);
|
|
5857
5888
|
|
|
5858
5889
|
// 3. Currently in-progress tasks across the swarm
|
|
@@ -5868,8 +5899,9 @@ export async function getConcurrentContext(): Promise<ConcurrentContext> {
|
|
|
5868
5899
|
`SELECT t.id, t.task, t.agentId, a.name as agentName, t.status, t.createdAt, t.progress
|
|
5869
5900
|
FROM agent_tasks t
|
|
5870
5901
|
LEFT JOIN agents a ON t.agentId = a.id
|
|
5871
|
-
WHERE t.status = 'in_progress'
|
|
5902
|
+
WHERE t.status = 'in_progress'${readableSql}
|
|
5872
5903
|
ORDER BY t.createdAt DESC`,
|
|
5904
|
+
readableParams,
|
|
5873
5905
|
);
|
|
5874
5906
|
|
|
5875
5907
|
return {
|
|
@@ -7157,15 +7189,27 @@ export async function deleteActiveSessionById(id: string): Promise<boolean> {
|
|
|
7157
7189
|
return result.changes > 0;
|
|
7158
7190
|
}
|
|
7159
7191
|
|
|
7160
|
-
export async function getActiveSessions(
|
|
7192
|
+
export async function getActiveSessions(
|
|
7193
|
+
agentId?: string,
|
|
7194
|
+
readableBy?: { userId: string | null },
|
|
7195
|
+
): Promise<ActiveSession[]> {
|
|
7196
|
+
const conditions: string[] = [];
|
|
7197
|
+
const params: string[] = [];
|
|
7161
7198
|
if (agentId) {
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
[agentId],
|
|
7165
|
-
);
|
|
7199
|
+
conditions.push("agentId = ?");
|
|
7200
|
+
params.push(agentId);
|
|
7166
7201
|
}
|
|
7202
|
+
if (readableBy) {
|
|
7203
|
+
const readable = readableSessionCondition(readableBy.userId, "active_sessions.taskId", {
|
|
7204
|
+
nullable: true,
|
|
7205
|
+
});
|
|
7206
|
+
conditions.push(readable.sql);
|
|
7207
|
+
params.push(...readable.params);
|
|
7208
|
+
}
|
|
7209
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
7167
7210
|
return await getDbClient().query<ActiveSession>(
|
|
7168
|
-
|
|
7211
|
+
`SELECT * FROM active_sessions ${where} ORDER BY startedAt DESC`,
|
|
7212
|
+
params,
|
|
7169
7213
|
);
|
|
7170
7214
|
}
|
|
7171
7215
|
|
|
@@ -12905,6 +12949,8 @@ interface ListRecentSessionsOpts {
|
|
|
12905
12949
|
contextKeyPrefix?: string;
|
|
12906
12950
|
/** When true, return slim `SessionListItemSummary` rows (default: full). */
|
|
12907
12951
|
slim?: boolean;
|
|
12952
|
+
/** Restrict to sessions this viewer may open (session sharing). */
|
|
12953
|
+
viewer?: SessionViewer;
|
|
12908
12954
|
}
|
|
12909
12955
|
|
|
12910
12956
|
export async function listRecentSessions(
|
|
@@ -12923,7 +12969,7 @@ export async function listRecentSessions(
|
|
|
12923
12969
|
const requestedByUserId = opts?.requestedByUserId?.trim() || undefined;
|
|
12924
12970
|
|
|
12925
12971
|
const conditions: string[] = ["r.parentTaskId IS NULL"];
|
|
12926
|
-
const params: (string | number)[] = [];
|
|
12972
|
+
const params: (string | number | null)[] = [];
|
|
12927
12973
|
|
|
12928
12974
|
if (sources.length > 0) {
|
|
12929
12975
|
conditions.push(`r.source IN (${sources.map(() => "?").join(", ")})`);
|
|
@@ -12942,6 +12988,11 @@ export async function listRecentSessions(
|
|
|
12942
12988
|
conditions.push(`r.contextKey LIKE ? ESCAPE '\\'`);
|
|
12943
12989
|
params.push(literalPrefixPattern(opts.contextKeyPrefix));
|
|
12944
12990
|
}
|
|
12991
|
+
if (opts?.viewer) {
|
|
12992
|
+
const viewer = sessionViewerCondition(opts.viewer);
|
|
12993
|
+
conditions.push(viewer.sql);
|
|
12994
|
+
params.push(...viewer.params);
|
|
12995
|
+
}
|
|
12945
12996
|
params.push(limit, offset);
|
|
12946
12997
|
|
|
12947
12998
|
const rootRows = await getDbClient().query<
|
|
@@ -13013,35 +13064,43 @@ export async function listRecentSessions(
|
|
|
13013
13064
|
* a plain count, no recursive chain walk needed.
|
|
13014
13065
|
*/
|
|
13015
13066
|
export async function countSessions(
|
|
13016
|
-
opts?: Pick<
|
|
13067
|
+
opts?: Pick<
|
|
13068
|
+
ListRecentSessionsOpts,
|
|
13069
|
+
"source" | "q" | "requestedByUserId" | "contextKeyPrefix" | "viewer"
|
|
13070
|
+
>,
|
|
13017
13071
|
): Promise<number> {
|
|
13018
13072
|
const sources = opts?.source?.filter((s) => s.length > 0) ?? [];
|
|
13019
13073
|
const q = opts?.q?.trim();
|
|
13020
13074
|
const requestedByUserId = opts?.requestedByUserId?.trim() || undefined;
|
|
13021
13075
|
|
|
13022
|
-
const conditions: string[] = ["parentTaskId IS NULL"];
|
|
13023
|
-
const params: string[] = [];
|
|
13076
|
+
const conditions: string[] = ["r.parentTaskId IS NULL"];
|
|
13077
|
+
const params: (string | null)[] = [];
|
|
13024
13078
|
|
|
13025
13079
|
if (sources.length > 0) {
|
|
13026
|
-
conditions.push(`source IN (${sources.map(() => "?").join(", ")})`);
|
|
13080
|
+
conditions.push(`r.source IN (${sources.map(() => "?").join(", ")})`);
|
|
13027
13081
|
params.push(...sources);
|
|
13028
13082
|
}
|
|
13029
13083
|
if (q && q.length > 0) {
|
|
13030
|
-
conditions.push("(lower(task) LIKE ? OR lower(COALESCE(title, '')) LIKE ?)");
|
|
13084
|
+
conditions.push("(lower(r.task) LIKE ? OR lower(COALESCE(r.title, '')) LIKE ?)");
|
|
13031
13085
|
const like = `%${q.toLowerCase()}%`;
|
|
13032
13086
|
params.push(like, like);
|
|
13033
13087
|
}
|
|
13034
13088
|
if (requestedByUserId) {
|
|
13035
|
-
conditions.push("requestedByUserId = ?");
|
|
13089
|
+
conditions.push("r.requestedByUserId = ?");
|
|
13036
13090
|
params.push(requestedByUserId);
|
|
13037
13091
|
}
|
|
13038
13092
|
if (opts?.contextKeyPrefix) {
|
|
13039
|
-
conditions.push(`contextKey LIKE ? ESCAPE '\\'`);
|
|
13093
|
+
conditions.push(`r.contextKey LIKE ? ESCAPE '\\'`);
|
|
13040
13094
|
params.push(literalPrefixPattern(opts.contextKeyPrefix));
|
|
13041
13095
|
}
|
|
13096
|
+
if (opts?.viewer) {
|
|
13097
|
+
const viewer = sessionViewerCondition(opts.viewer);
|
|
13098
|
+
conditions.push(viewer.sql);
|
|
13099
|
+
params.push(...viewer.params);
|
|
13100
|
+
}
|
|
13042
13101
|
|
|
13043
13102
|
const row = await getDbClient().get<{ count: number }>(
|
|
13044
|
-
`SELECT COUNT(*) AS count FROM agent_tasks WHERE ${conditions.join(" AND ")}`,
|
|
13103
|
+
`SELECT COUNT(*) AS count FROM agent_tasks r WHERE ${conditions.join(" AND ")}`,
|
|
13045
13104
|
params,
|
|
13046
13105
|
);
|
|
13047
13106
|
return row?.count ?? 0;
|
package/src/be/events.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { EventCategory, EventName, EventSource, EventStatus, SwarmEvent } from "../types";
|
|
2
2
|
import { type ScrubbedText, scrubObject, scrubSecrets } from "../utils/secret-scrubber";
|
|
3
3
|
import { getDbClient } from "./db";
|
|
4
|
+
import { readableSessionCondition } from "./db/tasks/read";
|
|
4
5
|
import type { DbExecutor } from "./db-client";
|
|
5
6
|
import { breaksJsonValidity } from "./scrub-json";
|
|
6
7
|
import { recordSkillInvocation } from "./skill-invocations";
|
|
@@ -262,10 +263,20 @@ export async function getEventsFiltered(filters: {
|
|
|
262
263
|
* the agent's event history.
|
|
263
264
|
*/
|
|
264
265
|
latestPerDataField?: { events: EventName[]; dataFields: string[] };
|
|
266
|
+
/** Only events outside sessions this user may not read; `userId: null` is a guest. */
|
|
267
|
+
readableBy?: { userId: string | null };
|
|
265
268
|
}): Promise<SwarmEvent[]> {
|
|
266
269
|
const conditions: string[] = [];
|
|
267
270
|
const params: (string | number)[] = [];
|
|
268
271
|
|
|
272
|
+
if (filters.readableBy) {
|
|
273
|
+
const readable = readableSessionCondition(filters.readableBy.userId, "events.taskId", {
|
|
274
|
+
nullable: true,
|
|
275
|
+
});
|
|
276
|
+
conditions.push(readable.sql);
|
|
277
|
+
params.push(...readable.params);
|
|
278
|
+
}
|
|
279
|
+
|
|
269
280
|
if (filters.category) {
|
|
270
281
|
conditions.push("category = ?");
|
|
271
282
|
params.push(filters.category);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
-- Session sharing. A session is a root task (parentTaskId IS NULL) plus its
|
|
2
|
+
-- descendants. A root with no active row stays private to its owner
|
|
3
|
+
-- (agent_tasks.requestedByUserId); a root with no owner stays open to all.
|
|
4
|
+
--
|
|
5
|
+
-- Rows are never deleted: revoking sets revokedAt/revokedBy, and an access
|
|
6
|
+
-- change revokes the active row and inserts a new one in one transaction, so
|
|
7
|
+
-- every row for a root is its share history. userId NULL means everyone.
|
|
8
|
+
CREATE TABLE session_shares (
|
|
9
|
+
id TEXT PRIMARY KEY,
|
|
10
|
+
rootTaskId TEXT NOT NULL REFERENCES agent_tasks(id) ON DELETE CASCADE,
|
|
11
|
+
userId TEXT REFERENCES users(id),
|
|
12
|
+
access TEXT NOT NULL CHECK (access IN ('read', 'write')),
|
|
13
|
+
createdAt TEXT NOT NULL,
|
|
14
|
+
revokedAt TEXT,
|
|
15
|
+
revokedBy TEXT REFERENCES users(id),
|
|
16
|
+
created_by TEXT REFERENCES users(id),
|
|
17
|
+
updated_by TEXT REFERENCES users(id)
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
-- At most one active row per (root, target); revoked rows pile up beside it.
|
|
21
|
+
CREATE UNIQUE INDEX session_shares_active
|
|
22
|
+
ON session_shares(rootTaskId, COALESCE(userId, '*')) WHERE revokedAt IS NULL;
|
|
23
|
+
CREATE INDEX session_shares_user ON session_shares(userId) WHERE revokedAt IS NULL;
|