@desplega.ai/agent-swarm 2.2.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-k0vwfyvn.js → acp-adapter-kedstxx6.js} +6 -6
- package/dist/{actions-w4pnb8vz.js → actions-vget6r1k.js} +18 -16
- package/dist/{amp-adapter-0cmxb2t5.js → amp-adapter-xeb5mktv.js} +2 -2
- package/dist/{app-zneem7g9.js → app-mqap8t8m.js} +3 -3
- package/dist/{assistant-wbzd2rnr.js → assistant-h0gv97mv.js} +20 -18
- package/dist/{boot-reembed-4kx69j3g.js → boot-reembed-qr8a894z.js} +1 -1
- package/dist/{boot-reembed-db0wwgy1.js → boot-reembed-rj85jjhw.js} +1 -1
- package/dist/{boot-scrub-sweep-tfv4d3mm.js → boot-scrub-sweep-eyfvn6v9.js} +3 -3
- package/dist/{boot-seeds-w2hmkxn5.js → boot-seeds-xs9a8dx1.js} +4 -4
- package/dist/{claude-adapter-5qrc2paq.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-7ra32em4.js → claude-managed-setup-gkfgks4j.js} +1 -1
- package/dist/{claude-sdk-session-cd02gc76.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-8c1m9mhy.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-vmbakbq0.js → cli-2rwet3r7.js} +1 -1
- package/dist/{cli-azs9bhc5.js → cli-3wjs22x3.js} +2 -2
- package/dist/{cli-tba8fxf9.js → cli-4fhyq711.js} +45 -36
- package/dist/{cli-ak4c12mh.js → cli-5ff2sjyt.js} +1 -1
- package/dist/{cli-6q7pyd3k.js → cli-5s5hx57y.js} +5 -3
- package/dist/{cli-h23mr2s6.js → cli-6ftrrwm1.js} +9 -371
- package/dist/{cli-9ac0mkg5.js → cli-8b9wvjps.js} +1 -1
- package/dist/cli-8cxkw1f5.js +382 -0
- package/dist/{cli-xhgtay7w.js → cli-8rwvksbc.js} +2 -2
- package/dist/{cli-e09hj50q.js → cli-8wkb09q5.js} +11 -11
- package/dist/{cli-ksrphveb.js → cli-a1esmmee.js} +14 -2
- package/dist/{cli-xnp2vkx7.js → cli-avmqhatn.js} +7 -7
- package/dist/{cli-xx13gqf3.js → cli-b80a1100.js} +1 -1
- package/dist/{cli-z1hh99j0.js → cli-bfc3k1x5.js} +3 -3
- package/dist/{cli-0k9rcwfy.js → cli-bhwa8rzc.js} +33 -15
- 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-jrk8mj0t.js → cli-ecshf504.js} +2 -2
- package/dist/{cli-raejy0mp.js → cli-eqgfs202.js} +1 -1
- package/dist/{cli-ppapy34w.js → cli-f3nx1h8z.js} +2 -2
- package/dist/{cli-c9vrwcpb.js → cli-ffbrkh1r.js} +74 -35
- package/dist/{cli-mnry16kh.js → cli-kk2r80n5.js} +4 -4
- package/dist/{cli-c0dpzwnx.js → cli-kz116kvw.js} +3 -3
- package/dist/{cli-e5y285rg.js → cli-mvg2hf8p.js} +4 -4
- package/dist/{cli-2jm992rm.js → cli-mz1ezzzh.js} +2 -2
- package/dist/{cli-dw0njd9q.js → cli-nme7wery.js} +6 -6
- package/dist/{cli-mvsfbbgq.js → cli-nrrg0fvy.js} +18 -4
- package/dist/{cli-8jtrekpy.js → cli-p1m2b6gc.js} +7 -7
- package/dist/{cli-4c7k75rq.js → cli-rg7ntypm.js} +378 -84
- package/dist/{cli-z3dk189k.js → cli-rsd7ga7v.js} +10 -8
- package/dist/{cli-etvn5ej2.js → cli-sppc0cmw.js} +1 -1
- package/dist/{cli-8w65vwee.js → cli-tg9ksbn8.js} +1 -1
- package/dist/{cli-th8krqbf.js → cli-tgvpmnwz.js} +2 -2
- package/dist/{cli-hwc4a9xm.js → cli-tj4j2ts4.js} +6 -6
- package/dist/{cli-jhhb2v9n.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-5ks6x36q.js → cli-x1p5dhq2.js} +3 -3
- package/dist/{cli-bddxamg7.js → cli-x719j3vs.js} +220 -629
- package/dist/{cli-ztcxv5nv.js → cli-xfe63zx3.js} +1 -1
- package/dist/{cli-q6gw45wj.js → cli-ywyrffff.js} +1 -1
- package/dist/{cli-p46xdr1m.js → cli-zvze9rbm.js} +1 -1
- package/dist/cli.js +15 -15
- package/dist/{codex-adapter-m0fx3v3s.js → codex-adapter-pp2gq8gr.js} +3 -3
- package/dist/{codex-hook-x6tm6y5m.js → codex-hook-qwzccd64.js} +1 -1
- package/dist/{codex-session-runner-j9m2gfyy.js → codex-session-runner-0vzn09t8.js} +3 -3
- package/dist/{commands-ktnqwrv4.js → commands-psqhkttb.js} +2 -2
- package/dist/{controller-nex2he8v.js → controller-t20vz2k8.js} +2 -2
- package/dist/{controllers-connect-6yhwqv9v.js → controllers-connect-gkxmqw9d.js} +24 -20
- package/dist/{cursor-adapter-02s5ekva.js → cursor-adapter-3v542qe5.js} +1 -1
- package/dist/{db-jvr86v7f.js → db-2xbyenjr.js} +2 -2
- package/dist/{db-19tw9r5s.js → db-nc1rd869.js} +3 -2
- package/dist/{dispatcher-d3mnsg2y.js → dispatcher-ewceb8ts.js} +17 -15
- package/dist/{dsh-adapter-63rvej88.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-44cwer32.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-ysp907f6.js → grok-adapter-vj88xr23.js} +7 -7
- package/dist/handlers-zbs90bny.js +39 -0
- package/dist/{heartbeat-2etacsqq.js → heartbeat-t2m9r3wa.js} +20 -18
- package/dist/{hook-mzxxs5n2.js → hook-8femx1bg.js} +4 -4
- package/dist/{hook-3kvmax6d.js → hook-xa677f8r.js} +5 -5
- package/dist/{http-fn978m0g.js → http-xpmmq028.js} +940 -269
- package/dist/{index-e4tsa1h7.js → index-8pwc1jc8.js} +17 -15
- package/dist/{index-w2wr89xd.js → index-cdzyfrsp.js} +1 -1
- package/dist/{index-1r8hw4vd.js → index-dg2tmj99.js} +17 -15
- package/dist/index-nxa6me0q.js +41 -0
- package/dist/{index-039x2sag.js → index-s7ckdajq.js} +42 -14
- 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-d4nzs76x.js → keepalive-3nr4st3r.js} +3 -3
- package/dist/{lead-8h5n9k5f.js → lead-td8h2sfn.js} +33 -31
- package/dist/{maintenance-ea6jfw42.js → maintenance-verwpe2c.js} +1 -1
- package/dist/{oauth-refresh-sweep-3f25djq3.js → oauth-refresh-sweep-9q2t0y3q.js} +2 -2
- package/dist/{onboard-pvk7hdb2.js → onboard-aytqedmq.js} +2 -2
- package/dist/{opencode-adapter-eeesgfqk.js → opencode-adapter-f4hryjny.js} +4 -4
- package/dist/{otel-impl-qb7nhgdp.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-6ehwkcmp.js → pricing-refresh-5nvme3sm.js} +3 -3
- package/dist/{rbac-roles-km0f9cmm.js → rbac-roles-pd8c26m1.js} +5 -3
- package/dist/{rbac-roles-9648nh00.js → rbac-roles-vbpy21mg.js} +2 -2
- package/dist/{render-v2-xkze1wef.js → render-v2-7nqgvk2q.js} +3 -3
- package/dist/{resume-tq0bbvt5.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-rnrazxvd.js → setup-n39szm6b.js} +1 -1
- package/dist/{virtual-modules-wzk6jf3a.js → virtual-modules-wyp2k6z3.js} +2 -2
- package/dist/{work-objects-e75q2rqs.js → work-objects-jem2fre0.js} +20 -18
- package/dist/{worker-4ww4hjef.js → worker-wr6xgfr2.js} +33 -31
- package/openapi.json +1011 -115
- package/package.json +2 -2
- package/src/apps/action-caller.ts +6 -0
- package/src/apps/script-types.ts +16 -3
- package/src/be/asset-key-audit.ts +4 -13
- 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 +149 -66
- package/src/be/events.ts +11 -0
- package/src/be/migrations/227_inbox_item_state_last_read_at.sql +1 -0
- package/src/be/migrations/230_session_shares.sql +23 -0
- package/src/be/rbac-roles.ts +2 -0
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +22 -4
- 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/heartbeat/heartbeat.ts +25 -7
- 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/assets.ts +26 -4
- 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/inbox-state.ts +58 -8
- 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/kv-reserved-namespaces.ts +1 -0
- 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/asset-key-api.test.ts +128 -0
- package/src/tests/asset-key-invariants.test.ts +179 -3
- package/src/tests/asset-key-migration.test.ts +35 -0
- 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/heartbeat-checklist.test.ts +88 -0
- package/src/tests/heartbeat-jev-gate.test.ts +394 -10
- package/src/tests/inbox-state.test.ts +167 -0
- package/src/tests/kv-tool.test.ts +15 -0
- 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/script-connections.test.ts +2 -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 +37 -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/templates/skills/code-quality/content.md +2 -2
- package/templates/skills/pages/content.md +4 -0
- package/templates/skills/swarm-scripts/SKILL.md +6 -0
- package/templates/skills/swarm-scripts/content.md +6 -0
- package/dist/handlers-t5pbabwf.js +0 -37
- package/dist/index-qyjbb7br.js +0 -39
- package/dist/{cli-n5mrsg0f.js → cli-60cgh1rv.js} +3 -3
- /package/dist/{cli-j0v75y13.js → cli-y83zk5sb.js} +0 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { getDbClient } from "./db";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Session sharing store. A session is a root task (`parentTaskId IS NULL`) and
|
|
5
|
+
* its descendants; its owner is the root's `requestedByUserId`.
|
|
6
|
+
*
|
|
7
|
+
* Rows in `session_shares` are never deleted. Revoking sets `revokedAt`, and an
|
|
8
|
+
* access change revokes the active row and inserts a new one in the same
|
|
9
|
+
* transaction, so the rows for a root are its full share history.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export type SessionShareAccess = "read" | "write";
|
|
13
|
+
export type SessionAccess = "owner" | SessionShareAccess | "none";
|
|
14
|
+
export type SessionEveryoneAccess = SessionShareAccess | "none";
|
|
15
|
+
|
|
16
|
+
export type SessionShareRow = {
|
|
17
|
+
id: string;
|
|
18
|
+
rootTaskId: string;
|
|
19
|
+
/** NULL = everyone. */
|
|
20
|
+
userId: string | null;
|
|
21
|
+
access: SessionShareAccess;
|
|
22
|
+
createdAt: string;
|
|
23
|
+
revokedAt: string | null;
|
|
24
|
+
revokedBy: string | null;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type SessionRoot = { id: string; requestedByUserId: string | null };
|
|
28
|
+
|
|
29
|
+
export type SessionSharesState = {
|
|
30
|
+
everyone: SessionEveryoneAccess;
|
|
31
|
+
users: { userId: string; access: SessionShareAccess; createdAt: string }[];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type DesiredSessionShares = {
|
|
35
|
+
everyone: SessionEveryoneAccess;
|
|
36
|
+
users: { userId: string; access: SessionShareAccess }[];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const RANK: Record<SessionAccess, number> = { none: 0, read: 1, write: 2, owner: 3 };
|
|
40
|
+
|
|
41
|
+
/** True when `access` grants at least `needed`. */
|
|
42
|
+
export function hasSessionAccess(access: SessionAccess, needed: SessionAccess): boolean {
|
|
43
|
+
return RANK[access] >= RANK[needed];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The session root of any task: walk `parentTaskId` up to the topmost task
|
|
48
|
+
* that still exists. No depth cap, so a chain of any length resolves to its
|
|
49
|
+
* actual root; a corrupt parent cycle has no top and resolves to null.
|
|
50
|
+
*/
|
|
51
|
+
export async function getSessionRoot(taskId: string): Promise<SessionRoot | null> {
|
|
52
|
+
return (await getSessionRoots([taskId])).get(taskId) ?? null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const ROOT_LOOKUP_CHUNK = 500;
|
|
56
|
+
|
|
57
|
+
type AncestorRow = { id: string; parentTaskId: string | null; requestedByUserId: string | null };
|
|
58
|
+
|
|
59
|
+
/** `getSessionRoot` for many tasks; unknown tasks and tasks in a cycle are absent. */
|
|
60
|
+
export async function getSessionRoots(taskIds: string[]): Promise<Map<string, SessionRoot>> {
|
|
61
|
+
const ids = [...new Set(taskIds)];
|
|
62
|
+
// Every ancestor once (`UNION` drops revisits, which also ends a cycle),
|
|
63
|
+
// then resolve each chain in memory: linear in the tasks involved.
|
|
64
|
+
const tasks = new Map<string, AncestorRow>();
|
|
65
|
+
for (let start = 0; start < ids.length; start += ROOT_LOOKUP_CHUNK) {
|
|
66
|
+
const chunk = ids.slice(start, start + ROOT_LOOKUP_CHUNK).filter((id) => !tasks.has(id));
|
|
67
|
+
if (chunk.length === 0) continue;
|
|
68
|
+
const rows = await getDbClient().query<AncestorRow>(
|
|
69
|
+
`WITH RECURSIVE up(id) AS (
|
|
70
|
+
SELECT id FROM agent_tasks WHERE id IN (${chunk.map(() => "?").join(", ")})
|
|
71
|
+
UNION
|
|
72
|
+
SELECT t.parentTaskId FROM agent_tasks t JOIN up ON t.id = up.id
|
|
73
|
+
WHERE t.parentTaskId IS NOT NULL
|
|
74
|
+
)
|
|
75
|
+
SELECT t.id, t.parentTaskId, t.requestedByUserId
|
|
76
|
+
FROM up CROSS JOIN agent_tasks t ON t.id = up.id`,
|
|
77
|
+
chunk,
|
|
78
|
+
);
|
|
79
|
+
for (const row of rows) tasks.set(row.id, row);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const resolved = new Map<string, SessionRoot | null>();
|
|
83
|
+
const rootOf = (taskId: string): SessionRoot | null => {
|
|
84
|
+
const path: string[] = [];
|
|
85
|
+
const onPath = new Set<string>();
|
|
86
|
+
let current = tasks.get(taskId);
|
|
87
|
+
let root: SessionRoot | null = null;
|
|
88
|
+
while (current) {
|
|
89
|
+
const known = resolved.get(current.id);
|
|
90
|
+
if (known !== undefined) {
|
|
91
|
+
root = known;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
if (onPath.has(current.id)) break;
|
|
95
|
+
path.push(current.id);
|
|
96
|
+
onPath.add(current.id);
|
|
97
|
+
const parent = current.parentTaskId ? tasks.get(current.parentTaskId) : undefined;
|
|
98
|
+
if (!parent) {
|
|
99
|
+
root = { id: current.id, requestedByUserId: current.requestedByUserId };
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
current = parent;
|
|
103
|
+
}
|
|
104
|
+
for (const id of path) resolved.set(id, root);
|
|
105
|
+
return root;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const roots = new Map<string, SessionRoot>();
|
|
109
|
+
for (const id of ids) {
|
|
110
|
+
const root = rootOf(id);
|
|
111
|
+
if (root) roots.set(id, root);
|
|
112
|
+
}
|
|
113
|
+
return roots;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* What `actingUserId` may do in the session rooted at `root`.
|
|
118
|
+
*
|
|
119
|
+
* - `owner` for the root's requester, and for every user when the root has no
|
|
120
|
+
* owner (Slack and schedule sessions stay open, as before sharing existed).
|
|
121
|
+
* - Otherwise the best active share naming the user or everyone; `none` when
|
|
122
|
+
* there is none. A root with no rows is private to its owner.
|
|
123
|
+
*
|
|
124
|
+
* A null `actingUserId` is a viewer with no user (a guest page session): it
|
|
125
|
+
* gets ownerless roots and everyone shares only. Callers that act for no user
|
|
126
|
+
* (agents, operator-key calls with no picked dashboard user) skip this check
|
|
127
|
+
* entirely; see `resolveSessionActor`.
|
|
128
|
+
*/
|
|
129
|
+
export async function sessionAccessForRoot(
|
|
130
|
+
actingUserId: string | null,
|
|
131
|
+
root: SessionRoot,
|
|
132
|
+
): Promise<SessionAccess> {
|
|
133
|
+
if (!root.requestedByUserId) return "owner";
|
|
134
|
+
if (actingUserId && root.requestedByUserId === actingUserId) return "owner";
|
|
135
|
+
const rows = await getDbClient().query<{ access: SessionShareAccess }>(
|
|
136
|
+
`SELECT access FROM session_shares
|
|
137
|
+
WHERE rootTaskId = ? AND revokedAt IS NULL AND (userId IS NULL OR userId = ?)`,
|
|
138
|
+
[root.id, actingUserId],
|
|
139
|
+
);
|
|
140
|
+
if (rows.some((row) => row.access === "write")) return "write";
|
|
141
|
+
return rows.length > 0 ? "read" : "none";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** `sessionAccessForRoot` for any task in the session; `none` for an unknown task. */
|
|
145
|
+
export async function sessionAccess(
|
|
146
|
+
actingUserId: string | null,
|
|
147
|
+
taskId: string,
|
|
148
|
+
): Promise<SessionAccess> {
|
|
149
|
+
const root = await getSessionRoot(taskId);
|
|
150
|
+
return root ? sessionAccessForRoot(actingUserId, root) : "none";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function listActiveSessionShares(rootTaskId: string): Promise<SessionShareRow[]> {
|
|
154
|
+
return getDbClient().query<SessionShareRow>(
|
|
155
|
+
`SELECT id, rootTaskId, userId, access, createdAt, revokedAt, revokedBy
|
|
156
|
+
FROM session_shares
|
|
157
|
+
WHERE rootTaskId = ? AND revokedAt IS NULL
|
|
158
|
+
ORDER BY createdAt, rowid`,
|
|
159
|
+
[rootTaskId],
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Every row for a root, revoked ones included, oldest first. */
|
|
164
|
+
export async function listSessionShareHistory(rootTaskId: string): Promise<SessionShareRow[]> {
|
|
165
|
+
return getDbClient().query<SessionShareRow>(
|
|
166
|
+
`SELECT id, rootTaskId, userId, access, createdAt, revokedAt, revokedBy
|
|
167
|
+
FROM session_shares
|
|
168
|
+
WHERE rootTaskId = ?
|
|
169
|
+
ORDER BY createdAt, rowid`,
|
|
170
|
+
[rootTaskId],
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function toSessionSharesState(rows: SessionShareRow[]): SessionSharesState {
|
|
175
|
+
const everyone = rows.find((row) => row.userId === null);
|
|
176
|
+
return {
|
|
177
|
+
everyone: everyone?.access ?? "none",
|
|
178
|
+
users: rows
|
|
179
|
+
.filter((row) => row.userId !== null)
|
|
180
|
+
.map((row) => ({ userId: row.userId!, access: row.access, createdAt: row.createdAt })),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Make the active shares of `root` equal `desired`, in one write transaction.
|
|
186
|
+
* A target whose access changes has its active row revoked and a new row
|
|
187
|
+
* inserted; a target no longer listed is revoked; nothing is deleted or
|
|
188
|
+
* updated in place apart from revocation. The owner is never stored.
|
|
189
|
+
*/
|
|
190
|
+
export async function setSessionShares(
|
|
191
|
+
root: SessionRoot,
|
|
192
|
+
desired: DesiredSessionShares,
|
|
193
|
+
actorUserId: string | null,
|
|
194
|
+
): Promise<SessionSharesState> {
|
|
195
|
+
const want = new Map<string, SessionShareAccess>();
|
|
196
|
+
if (desired.everyone !== "none") want.set("*", desired.everyone);
|
|
197
|
+
for (const user of desired.users) {
|
|
198
|
+
if (user.userId !== root.requestedByUserId) want.set(user.userId, user.access);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return getDbClient().transaction(async (tx) => {
|
|
202
|
+
const now = new Date().toISOString();
|
|
203
|
+
const active = await tx.query<SessionShareRow>(
|
|
204
|
+
`SELECT id, rootTaskId, userId, access, createdAt, revokedAt, revokedBy
|
|
205
|
+
FROM session_shares WHERE rootTaskId = ? AND revokedAt IS NULL`,
|
|
206
|
+
[root.id],
|
|
207
|
+
);
|
|
208
|
+
const kept = new Set<string>();
|
|
209
|
+
for (const row of active) {
|
|
210
|
+
const target = row.userId ?? "*";
|
|
211
|
+
if (want.get(target) === row.access) {
|
|
212
|
+
kept.add(target);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
await tx.run(
|
|
216
|
+
"UPDATE session_shares SET revokedAt = ?, revokedBy = ?, updated_by = ? WHERE id = ?",
|
|
217
|
+
[now, actorUserId, actorUserId, row.id],
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
for (const [target, access] of want) {
|
|
221
|
+
if (kept.has(target)) continue;
|
|
222
|
+
await tx.run(
|
|
223
|
+
`INSERT INTO session_shares (id, rootTaskId, userId, access, createdAt, created_by, updated_by)
|
|
224
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
225
|
+
[
|
|
226
|
+
crypto.randomUUID(),
|
|
227
|
+
root.id,
|
|
228
|
+
target === "*" ? null : target,
|
|
229
|
+
access,
|
|
230
|
+
now,
|
|
231
|
+
actorUserId,
|
|
232
|
+
actorUserId,
|
|
233
|
+
],
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
const rows = await tx.query<SessionShareRow>(
|
|
237
|
+
`SELECT id, rootTaskId, userId, access, createdAt, revokedAt, revokedBy
|
|
238
|
+
FROM session_shares WHERE rootTaskId = ? AND revokedAt IS NULL
|
|
239
|
+
ORDER BY createdAt, rowid`,
|
|
240
|
+
[root.id],
|
|
241
|
+
);
|
|
242
|
+
return toSessionSharesState(rows);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { validateChatSdkAdaptersValue } from "../integrations/chat-sdk/schema";
|
|
2
|
+
import { parseTelemetryRanking, TELEMETRY_RANKING_KEY } from "../ranking/telemetry-ranking";
|
|
2
3
|
import { validateQuickJSPoolSize } from "../scripts-runtime/executors/quickjs-pool-config";
|
|
3
4
|
import { normalizeSlackReactionShortcode } from "../slack/reaction-shortcode";
|
|
4
5
|
import { ProviderNameSchema } from "../types";
|
|
@@ -69,15 +70,24 @@ export function isApiOnlyConfigKey(key: string): boolean {
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
/** Config rows owned by dedicated API surfaces, not the generic config API. */
|
|
72
|
-
export const INTERNAL_CONFIG_KEYS = new Set([
|
|
73
|
+
export const INTERNAL_CONFIG_KEYS = new Set([
|
|
74
|
+
"onboarding_state",
|
|
75
|
+
"runtime_owner_id",
|
|
76
|
+
TELEMETRY_RANKING_KEY,
|
|
77
|
+
]);
|
|
73
78
|
|
|
74
79
|
export function isInternalConfigKey(key: string): boolean {
|
|
75
80
|
return INTERNAL_CONFIG_KEYS.has(key.toLowerCase());
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
export function internalConfigKeyError(key: string): Error {
|
|
84
|
+
const lower = key.toLowerCase();
|
|
79
85
|
const owner =
|
|
80
|
-
|
|
86
|
+
lower === "runtime_owner_id"
|
|
87
|
+
? "the runtime supervisor"
|
|
88
|
+
: lower === TELEMETRY_RANKING_KEY
|
|
89
|
+
? "/api/ranking"
|
|
90
|
+
: "/api/onboarding";
|
|
81
91
|
return new Error(`Key '${key}' is managed by ${owner}`);
|
|
82
92
|
}
|
|
83
93
|
|
|
@@ -301,6 +311,16 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
|
|
|
301
311
|
return "Invalid TASK_TOOL_MANIFESTS (expected JSON with taskTypes and/or schedules maps, each selecting at most 16 known swarm tools)";
|
|
302
312
|
}
|
|
303
313
|
},
|
|
314
|
+
// Owned by /api/ranking (an internal key); parsed here as defense in depth.
|
|
315
|
+
[TELEMETRY_RANKING_KEY.toUpperCase()]: (value) => {
|
|
316
|
+
try {
|
|
317
|
+
if (typeof value !== "string") throw new Error("Expected JSON string");
|
|
318
|
+
parseTelemetryRanking(value);
|
|
319
|
+
return null;
|
|
320
|
+
} catch {
|
|
321
|
+
return "Invalid telemetry_ranking (expected the v1 ranking listing JSON)";
|
|
322
|
+
}
|
|
323
|
+
},
|
|
304
324
|
// MCP tool allowlist: comma-separated names or a JSON array of strings.
|
|
305
325
|
// Unknown names are accepted here and warned about at session init.
|
|
306
326
|
SWARM_ENABLED_TOOLS: (value) => {
|
|
@@ -405,6 +425,7 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
|
|
|
405
425
|
},
|
|
406
426
|
...booleanValidators([
|
|
407
427
|
"SWARM_USED_BY_CONSENT",
|
|
428
|
+
"PUBLIC_RANKING_ENABLED",
|
|
408
429
|
"SWARM_DEV_MODE",
|
|
409
430
|
"MULTI_RUNTIME_ENABLED",
|
|
410
431
|
"STEERING_ENABLED",
|
package/src/commands/runner.ts
CHANGED
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
skillFromToolArgs,
|
|
56
56
|
} from "../providers/skill-invoke.ts";
|
|
57
57
|
import type { SteerDeliveryResult } from "../providers/types.ts";
|
|
58
|
+
import { rankingUsageProps } from "../ranking/usage.ts";
|
|
58
59
|
import { WorkerRealtimeClient } from "../realtime/worker-client";
|
|
59
60
|
import { resumedTaskModelSelection, type TaskModelPinFields } from "../tasks/model-pin.ts";
|
|
60
61
|
import { initTelemetry, telemetry } from "../telemetry.ts";
|
|
@@ -1228,6 +1229,10 @@ export const RELOADABLE_ENV_KEYS: ReadonlySet<string> = new Set([
|
|
|
1228
1229
|
"TEMPLATE_REGISTRY_URL",
|
|
1229
1230
|
"SLACK_DISABLE",
|
|
1230
1231
|
"SWARM_ORG_NAME",
|
|
1232
|
+
// Gate the public-ranking `ranking.usage` emit, so clearing the website or
|
|
1233
|
+
// turning the setting off stops it on running workers.
|
|
1234
|
+
"SWARM_ORG_WEBSITE",
|
|
1235
|
+
"PUBLIC_RANKING_ENABLED",
|
|
1231
1236
|
// pi reads these from process.env for its traits and its session.
|
|
1232
1237
|
"PI_TOOL_DEFERRAL",
|
|
1233
1238
|
"PI_CODEMODE",
|
|
@@ -4923,6 +4928,12 @@ async function spawnProviderProcess(
|
|
|
4923
4928
|
},
|
|
4924
4929
|
{ userId: opts.requestedByUserId ?? null },
|
|
4925
4930
|
);
|
|
4931
|
+
// Public ranking: token counts only, while the install is opted in.
|
|
4932
|
+
const rankingUsage = rankingUsageProps(result.cost, {
|
|
4933
|
+
model: result.cost.model,
|
|
4934
|
+
provider: result.cost.provider ?? opts.harnessProvider,
|
|
4935
|
+
});
|
|
4936
|
+
if (rankingUsage) telemetry.ranking("usage", rankingUsage);
|
|
4926
4937
|
}
|
|
4927
4938
|
|
|
4928
4939
|
// Post completion context usage snapshot
|
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
settleSupersededTaskDependents,
|
|
49
49
|
supersedeTask,
|
|
50
50
|
updateAgentStatus,
|
|
51
|
+
upsertKv,
|
|
51
52
|
} from "../be/db";
|
|
52
53
|
import { repointTrackerSyncBySwarmId } from "../be/db-queries/tracker";
|
|
53
54
|
import { poolTaskRunsOnHarness } from "../be/model-validation";
|
|
@@ -1461,7 +1462,10 @@ export function isEffectivelyEmpty(content: string): boolean {
|
|
|
1461
1462
|
/**
|
|
1462
1463
|
* Gather current system status as a markdown string for the lead's checklist task.
|
|
1463
1464
|
*/
|
|
1464
|
-
export async function gatherSystemStatus(options?: {
|
|
1465
|
+
export async function gatherSystemStatus(options?: {
|
|
1466
|
+
isBootTriage?: boolean;
|
|
1467
|
+
reportedFailures?: Array<{ id: string; line: string }>;
|
|
1468
|
+
}): Promise<string> {
|
|
1465
1469
|
const stats = await getTaskStats();
|
|
1466
1470
|
const stalledTasks = await getStalledInProgressTasks(stallThresholdMinutes());
|
|
1467
1471
|
const agents = await getAllAgents();
|
|
@@ -1494,6 +1498,7 @@ export async function gatherSystemStatus(options?: { isBootTriage?: boolean }):
|
|
|
1494
1498
|
|
|
1495
1499
|
// Recent failures with reasons and pattern detection (last 6 hours)
|
|
1496
1500
|
const recentFailures = await getRecentFailedTasks(6);
|
|
1501
|
+
const renderedFailures = recentFailures.slice(0, 10);
|
|
1497
1502
|
if (recentFailures.length > 0) {
|
|
1498
1503
|
sections.push("");
|
|
1499
1504
|
sections.push("## Recent Failures (last 6h) [auto-generated]");
|
|
@@ -1520,12 +1525,12 @@ export async function gatherSystemStatus(options?: { isBootTriage?: boolean }):
|
|
|
1520
1525
|
|
|
1521
1526
|
// List individual failures (max 10)
|
|
1522
1527
|
sections.push("");
|
|
1523
|
-
for (const task of
|
|
1528
|
+
for (const task of renderedFailures) {
|
|
1524
1529
|
const agentSlice = task.agentId?.slice(0, 8) ?? "unassigned";
|
|
1525
1530
|
const reason = task.failureReason?.slice(0, 100) ?? "no reason";
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
);
|
|
1531
|
+
const line = `- [${task.id.slice(0, 8)}] "${task.task.slice(0, 50)}" — agent: ${agentSlice}, reason: ${reason}, at: ${task.finishedAt}`;
|
|
1532
|
+
sections.push(line);
|
|
1533
|
+
options?.reportedFailures?.push({ id: task.id, line });
|
|
1529
1534
|
}
|
|
1530
1535
|
if (recentFailures.length > 10) {
|
|
1531
1536
|
sections.push(`- ... and ${recentFailures.length - 10} more`);
|
|
@@ -1786,7 +1791,8 @@ export async function checkHeartbeatChecklist(): Promise<void> {
|
|
|
1786
1791
|
return;
|
|
1787
1792
|
}
|
|
1788
1793
|
|
|
1789
|
-
|
|
1794
|
+
const reportedFailures: Array<{ id: string; line: string }> = [];
|
|
1795
|
+
let systemStatus = await gatherSystemStatus({ reportedFailures });
|
|
1790
1796
|
// Shadow mode leaves the prompt untouched so the Lead's own quiet/non-quiet
|
|
1791
1797
|
// outcome stays an independent check on the gate's verdict.
|
|
1792
1798
|
if (gateMode === "enforce" && gate?.verdict === "wake" && gate.summary) {
|
|
@@ -1803,7 +1809,7 @@ export async function checkHeartbeatChecklist(): Promise<void> {
|
|
|
1803
1809
|
|
|
1804
1810
|
if (result.skipped) return;
|
|
1805
1811
|
|
|
1806
|
-
await createTaskExtended(result.text, {
|
|
1812
|
+
const task = await createTaskExtended(result.text, {
|
|
1807
1813
|
agentId: lead.id,
|
|
1808
1814
|
routingReason: "skill",
|
|
1809
1815
|
routingSource: "engine_default",
|
|
@@ -1815,6 +1821,18 @@ export async function checkHeartbeatChecklist(): Promise<void> {
|
|
|
1815
1821
|
priority: 60,
|
|
1816
1822
|
});
|
|
1817
1823
|
|
|
1824
|
+
try {
|
|
1825
|
+
await upsertKv({
|
|
1826
|
+
namespace: "heartbeat-checklist:reported-failures",
|
|
1827
|
+
key: task.id,
|
|
1828
|
+
value: reportedFailures.filter(({ line }) => result.text.includes(line)).map(({ id }) => id),
|
|
1829
|
+
valueType: "json",
|
|
1830
|
+
expiresAt: Date.now() + 7 * 24 * 3600e3,
|
|
1831
|
+
});
|
|
1832
|
+
} catch (err) {
|
|
1833
|
+
console.error(`[Heartbeat] Failed to save reported failures for checklist ${task.id}:`, err);
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1818
1836
|
console.log(`[Heartbeat] Checklist task created for lead ${lead.name}`);
|
|
1819
1837
|
}
|
|
1820
1838
|
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
} from "../be/db";
|
|
14
14
|
import { ActiveSessionSchema, AgentTaskSchema } from "../types";
|
|
15
15
|
import { isMultiRuntimeEnabled } from "../utils/multi-runtime";
|
|
16
|
-
import { route } from "./route-def";
|
|
16
|
+
import { bodyTask, route } from "./route-def";
|
|
17
|
+
import { readableByFor, resolveSessionActor } from "./session-access";
|
|
17
18
|
import { jsonError } from "./utils";
|
|
18
19
|
|
|
19
20
|
// ─── Route Definitions ───────────────────────────────────────────────────────
|
|
@@ -39,6 +40,7 @@ const createActiveSession = route({
|
|
|
39
40
|
method: "post",
|
|
40
41
|
path: "/api/active-sessions",
|
|
41
42
|
pattern: ["api", "active-sessions"],
|
|
43
|
+
session: bodyTask("taskId"),
|
|
42
44
|
summary: "Create a new active session",
|
|
43
45
|
tags: ["Active Sessions"],
|
|
44
46
|
body: z.object({
|
|
@@ -60,6 +62,7 @@ const deleteSessionByTask = route({
|
|
|
60
62
|
method: "delete",
|
|
61
63
|
path: "/api/active-sessions/by-task/{taskId}",
|
|
62
64
|
pattern: ["api", "active-sessions", "by-task", null],
|
|
65
|
+
session: { taskParam: "taskId" },
|
|
63
66
|
summary: "Delete active session by task ID",
|
|
64
67
|
tags: ["Active Sessions"],
|
|
65
68
|
params: z.object({ taskId: z.string() }),
|
|
@@ -72,6 +75,7 @@ const deleteSessionById = route({
|
|
|
72
75
|
method: "delete",
|
|
73
76
|
path: "/api/active-sessions/{id}",
|
|
74
77
|
pattern: ["api", "active-sessions", null],
|
|
78
|
+
session: { activeSessionParam: "id" },
|
|
75
79
|
summary: "Delete active session by ID",
|
|
76
80
|
tags: ["Active Sessions"],
|
|
77
81
|
params: z.object({ id: z.string() }),
|
|
@@ -84,6 +88,7 @@ const heartbeatSession = route({
|
|
|
84
88
|
method: "put",
|
|
85
89
|
path: "/api/active-sessions/heartbeat/{taskId}",
|
|
86
90
|
pattern: ["api", "active-sessions", "heartbeat", null],
|
|
91
|
+
session: { taskParam: "taskId" },
|
|
87
92
|
summary: "Update heartbeat for an active session",
|
|
88
93
|
tags: ["Active Sessions"],
|
|
89
94
|
params: z.object({ taskId: z.string() }),
|
|
@@ -96,6 +101,7 @@ const updateProviderSession = route({
|
|
|
96
101
|
method: "put",
|
|
97
102
|
path: "/api/active-sessions/provider-session/{taskId}",
|
|
98
103
|
pattern: ["api", "active-sessions", "provider-session", null],
|
|
104
|
+
session: { taskParam: "taskId" },
|
|
99
105
|
summary: "Update provider session ID on an active session",
|
|
100
106
|
tags: ["Active Sessions"],
|
|
101
107
|
params: z.object({ taskId: z.string() }),
|
|
@@ -109,6 +115,7 @@ const cleanupSessions = route({
|
|
|
109
115
|
method: "post",
|
|
110
116
|
path: "/api/active-sessions/cleanup",
|
|
111
117
|
pattern: ["api", "active-sessions", "cleanup"],
|
|
118
|
+
session: { agentWide: "Deletes every session of an agent, or every stale session." },
|
|
112
119
|
summary: "Clean up stale sessions",
|
|
113
120
|
tags: ["Active Sessions"],
|
|
114
121
|
body: z
|
|
@@ -126,6 +133,7 @@ const recoverOrphanedTasks = route({
|
|
|
126
133
|
method: "post",
|
|
127
134
|
path: "/api/active-sessions/recover-orphaned-tasks",
|
|
128
135
|
pattern: ["api", "active-sessions", "recover-orphaned-tasks"],
|
|
136
|
+
session: { none: "Its handler admits only the calling agent, never a user." },
|
|
129
137
|
summary: "Recover orphaned in-progress tasks for an agent",
|
|
130
138
|
tags: ["Active Sessions"],
|
|
131
139
|
body: z.object({
|
|
@@ -153,7 +161,10 @@ export async function handleActiveSessions(
|
|
|
153
161
|
if (listActiveSessions.match(req.method, pathSegments)) {
|
|
154
162
|
const parsed = await listActiveSessions.parse(req, res, pathSegments, queryParams);
|
|
155
163
|
if (!parsed) return true;
|
|
156
|
-
const sessions = await getActiveSessions(
|
|
164
|
+
const sessions = await getActiveSessions(
|
|
165
|
+
parsed.query.agentId || undefined,
|
|
166
|
+
readableByFor(await resolveSessionActor(req, myAgentId)),
|
|
167
|
+
);
|
|
157
168
|
listActiveSessions.respond(res, 200, { sessions });
|
|
158
169
|
return true;
|
|
159
170
|
}
|
package/src/http/agents.ts
CHANGED
|
@@ -90,6 +90,7 @@ import { getRequestAuth } from "../utils/request-auth-context";
|
|
|
90
90
|
import { isRuntimePoolsEnabled } from "../utils/runtime-pools-flag";
|
|
91
91
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
92
92
|
import { route, runtimeInstanceHeader } from "./route-def";
|
|
93
|
+
import { readableByFor, resolveSessionActor } from "./session-access";
|
|
93
94
|
import { agentWithCapacity, json, jsonError } from "./utils";
|
|
94
95
|
|
|
95
96
|
function singleHeaderValue(req: IncomingMessage, name: string): string | undefined {
|
|
@@ -844,7 +845,7 @@ export async function handleAgentsRest(
|
|
|
844
845
|
res: ServerResponse,
|
|
845
846
|
pathSegments: string[],
|
|
846
847
|
queryParams: URLSearchParams,
|
|
847
|
-
|
|
848
|
+
myAgentId: string | undefined,
|
|
848
849
|
): Promise<boolean> {
|
|
849
850
|
if (listAgents.match(req.method, pathSegments)) {
|
|
850
851
|
const parsed = await listAgents.parse(req, res, pathSegments, queryParams);
|
|
@@ -853,7 +854,10 @@ export async function handleAgentsRest(
|
|
|
853
854
|
// List responses default to slim (no identity markdown); `?fields=full` restores it.
|
|
854
855
|
const slim = parsed.query.fields !== "full";
|
|
855
856
|
const agents = includeTasks
|
|
856
|
-
? await getAllAgentsWithTasks({
|
|
857
|
+
? await getAllAgentsWithTasks({
|
|
858
|
+
slim,
|
|
859
|
+
readableBy: readableByFor(await resolveSessionActor(req, myAgentId)),
|
|
860
|
+
})
|
|
857
861
|
: await getAllAgents({ slim });
|
|
858
862
|
const [agentsWithCapacity, transports] = await Promise.all([
|
|
859
863
|
Promise.all(agents.map(agentWithCapacity)),
|
|
@@ -1537,7 +1541,10 @@ export async function handleAgentsRest(
|
|
|
1537
1541
|
if (!parsed) return true;
|
|
1538
1542
|
const includeTasks = parsed.query.include === "tasks";
|
|
1539
1543
|
const agent = includeTasks
|
|
1540
|
-
? await getAgentWithTasks(
|
|
1544
|
+
? await getAgentWithTasks(
|
|
1545
|
+
parsed.params.id,
|
|
1546
|
+
readableByFor(await resolveSessionActor(req, myAgentId)),
|
|
1547
|
+
)
|
|
1541
1548
|
: await getAgentById(parsed.params.id);
|
|
1542
1549
|
|
|
1543
1550
|
if (!agent) {
|
package/src/http/all-routes.ts
CHANGED
package/src/http/api-keys.ts
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
SUBSCRIPTION_PLANS,
|
|
21
21
|
SUBSCRIPTION_PLANS_CHECKED_AT,
|
|
22
22
|
} from "../utils/subscription-plans";
|
|
23
|
-
import { route } from "./route-def";
|
|
23
|
+
import { bodyTask, route } from "./route-def";
|
|
24
24
|
import { clearUsageCache } from "./usage-cache";
|
|
25
25
|
import { jsonError } from "./utils";
|
|
26
26
|
|
|
@@ -36,6 +36,7 @@ const reportUsage = route({
|
|
|
36
36
|
method: "post",
|
|
37
37
|
path: "/api/keys/report-usage",
|
|
38
38
|
pattern: ["api", "keys", "report-usage"],
|
|
39
|
+
session: bodyTask("taskId"),
|
|
39
40
|
summary: "Record which API key was used for a task",
|
|
40
41
|
tags: ["API Keys"],
|
|
41
42
|
body: z.object({
|
|
@@ -82,6 +83,7 @@ const reportAuthFailure = route({
|
|
|
82
83
|
method: "post",
|
|
83
84
|
path: "/api/keys/report-auth-failure",
|
|
84
85
|
pattern: ["api", "keys", "report-auth-failure"],
|
|
86
|
+
session: bodyTask("taskId"),
|
|
85
87
|
summary: "Record an auth failure for a pooled key; bench it after 2 in a row",
|
|
86
88
|
tags: ["API Keys"],
|
|
87
89
|
body: z.object({
|
|
@@ -23,7 +23,7 @@ import { getRequestAuth } from "../utils/request-auth-context";
|
|
|
23
23
|
import { cancelApprovalRequest } from "../workflows/approval-cancel";
|
|
24
24
|
import { createApprovalFollowUpTask } from "../workflows/approval-notifications";
|
|
25
25
|
import { workflowEventBus } from "../workflows/event-bus";
|
|
26
|
-
import { route } from "./route-def";
|
|
26
|
+
import { bodyTask, route } from "./route-def";
|
|
27
27
|
import { jsonError } from "./utils";
|
|
28
28
|
|
|
29
29
|
// ─── Route Definitions ───────────────────────────────────────────────────────
|
|
@@ -255,6 +255,7 @@ const createRoute = route({
|
|
|
255
255
|
method: "post",
|
|
256
256
|
path: "/api/approval-requests",
|
|
257
257
|
pattern: ["api", "approval-requests"],
|
|
258
|
+
session: bodyTask("sourceTaskId"),
|
|
258
259
|
summary: "Create a new approval request",
|
|
259
260
|
tags: ["ApprovalRequests"],
|
|
260
261
|
body: z.object({
|