@desplega.ai/agent-swarm 2.7.0 → 2.7.1
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/dist/{acp-adapter-ma1js5n7.js → acp-adapter-8dn5tq56.js} +6 -6
- package/dist/{actions-1vhmtvg9.js → actions-3r6gw7dy.js} +18 -18
- package/dist/{amp-adapter-sxqqvmdk.js → amp-adapter-wjtq413p.js} +3 -3
- package/dist/{anthropic-messages-txs190dd.js → anthropic-messages-n4cby1rz.js} +4 -4
- package/dist/{app-mwx1d5a9.js → app-d53yhykk.js} +3 -3
- package/dist/{assistant-yx1p6g82.js → assistant-ksrawj3k.js} +20 -20
- package/dist/{boot-reembed-7h78vmvv.js → boot-reembed-fxfw17m2.js} +10 -3
- package/dist/{boot-reembed-5h37a2c2.js → boot-reembed-k5y5xbj0.js} +15 -8
- package/dist/{boot-scrub-sweep-8mawd4gw.js → boot-scrub-sweep-se41j9fd.js} +3 -3
- package/dist/{boot-seeds-ca84dhnn.js → boot-seeds-r3kwwv8b.js} +4 -4
- package/dist/{claude-adapter-wqt4hfkq.js → claude-adapter-ahmyf231.js} +7 -7
- package/dist/{claude-managed-adapter-6fjqtr2z.js → claude-managed-adapter-5knamegc.js} +3 -3
- package/dist/{claude-managed-setup-rqv98sea.js → claude-managed-setup-mdc7dy6b.js} +2 -2
- package/dist/{claude-sdk-session-f29f3b2x.js → claude-sdk-session-n5wen9zk.js} +8728 -8599
- package/dist/{cli-m5e802r7.js → cli-0dqtv2jg.js} +2 -2
- package/dist/{cli-e1595bgx.js → cli-0tww7dnz.js} +2 -2
- package/dist/{cli-2y7v133v.js → cli-0w4e4v58.js} +7 -7
- package/dist/{cli-d20qvpr2.js → cli-2sjm3t4r.js} +1 -1
- package/dist/{cli-gf2850q7.js → cli-3cywb44q.js} +18 -1
- package/dist/{cli-8wkb09q5.js → cli-3k9jej0c.js} +2 -2
- package/dist/{cli-twctnkng.js → cli-3v2cyqb8.js} +3 -3
- package/dist/{cli-he9gbdx0.js → cli-5qt9vzmq.js} +39 -20
- package/dist/{cli-a6ds2epe.js → cli-5rp1w4cr.js} +1 -1
- package/dist/{cli-6srv653c.js → cli-6vwjsetx.js} +4 -4
- package/dist/{cli-1hhbzkbb.js → cli-6w2yrrc0.js} +8 -8
- package/dist/{cli-f12mr20z.js → cli-75tdydgv.js} +1 -1
- package/dist/{cli-j3y0x2n4.js → cli-8feswf2v.js} +43 -33
- package/dist/{cli-07q03xm2.js → cli-8wd3mbyj.js} +1 -1
- package/dist/{cli-ymjpkxgn.js → cli-9csfthbw.js} +8 -6
- package/dist/{cli-f1e0ptjj.js → cli-b41p764a.js} +21 -2
- package/dist/{cli-kv7sbc2c.js → cli-c1t533hh.js} +3 -3
- package/dist/{cli-54sj64fc.js → cli-c28jxpcv.js} +1 -1
- package/dist/{cli-8mbc9za9.js → cli-cb50g9cd.js} +1 -1
- package/dist/{cli-c668vvcf.js → cli-d8gaphtr.js} +1 -1
- package/dist/{cli-86ehn4s5.js → cli-dgtfysne.js} +2 -2
- package/dist/{cli-sjctjr48.js → cli-dscnhn18.js} +1 -1
- package/dist/{cli-j0ppywdr.js → cli-dv11pgpv.js} +12 -12
- package/dist/{cli-1m9x8dr5.js → cli-ekptgk2m.js} +5 -5
- package/dist/{cli-9gsm2rzp.js → cli-f3mzy662.js} +17 -17
- package/dist/{cli-s0mgx214.js → cli-fk7k8s9x.js} +2 -2
- package/dist/{cli-vbhgkw9h.js → cli-h3e7bhvn.js} +3 -3
- package/dist/{cli-yd2nreqw.js → cli-hkxqdmnb.js} +2 -2
- package/dist/{cli-8wmj18cz.js → cli-hpkvgdad.js} +7 -4
- package/dist/{cli-wqr7cea4.js → cli-hq2v4rr5.js} +37 -9
- package/dist/{cli-sgqm2582.js → cli-k1zb7raw.js} +5 -5
- package/dist/{cli-4rvr7zfx.js → cli-k4awgvzm.js} +5 -0
- package/dist/{cli-ay40dnj3.js → cli-k7d3hvya.js} +2 -2
- package/dist/{cli-j9bpmykh.js → cli-kbtdftbw.js} +5 -5
- package/dist/{cli-x37r7qwf.js → cli-kj69b9tw.js} +25 -5
- package/dist/{cli-gsfk58nw.js → cli-kk9jy4nm.js} +1 -1
- package/dist/{cli-39aet655.js → cli-m52t4cta.js} +1 -1
- package/dist/{cli-h5jeg46p.js → cli-mdadewqp.js} +5 -5
- package/dist/{cli-a89zdqz9.js → cli-nf6k60sn.js} +1 -1
- package/dist/{cli-v6shg7na.js → cli-nzdf09js.js} +1 -1
- package/dist/{cli-rr7zx19j.js → cli-prcyzecg.js} +12 -12
- package/dist/{cli-6a9qfa8b.js → cli-pzafyqtk.js} +118 -48
- package/dist/{cli-48gxjmcz.js → cli-qvaa1gjp.js} +87 -24
- package/dist/{cli-qj442fws.js → cli-rg4b2g6j.js} +1 -1
- package/dist/{cli-aypd09nf.js → cli-rm66vqdz.js} +1 -1
- package/dist/{cli-9pshx6rd.js → cli-s504c3w7.js} +1 -1
- package/dist/{cli-ptfq202x.js → cli-swkx9jm2.js} +3 -3
- package/dist/{cli-m30k9777.js → cli-thsaq4pd.js} +16 -16
- package/dist/{cli-60sty27p.js → cli-twjnyvs8.js} +2 -2
- package/dist/{cli-rd5m70gj.js → cli-typdpe8g.js} +4 -4
- package/dist/{cli-yxbsmx56.js → cli-vg8pwmsd.js} +6 -6
- package/dist/{cli-s09j9vp5.js → cli-vhpckzvp.js} +1 -1
- package/dist/{cli-dv97kdzv.js → cli-wdxgsdz5.js} +2 -2
- package/dist/{cli-7a6p4e65.js → cli-wtjg6je3.js} +1 -1
- package/dist/{cli-b3yzyc57.js → cli-x3cngb1h.js} +44 -12
- package/dist/{cli-6mgdba42.js → cli-xbz6mcg4.js} +2 -2
- package/dist/{cli-2pvzmas1.js → cli-y4tdxsqb.js} +3 -3
- package/dist/{cli-f4dbh7a5.js → cli-yqrsp3ny.js} +7 -7
- package/dist/cli.js +27 -19
- package/dist/{codex-adapter-zk59x2zh.js → codex-adapter-7wepzq15.js} +5 -5
- package/dist/{codex-hook-xyjhvxwd.js → codex-hook-pbdh4xhx.js} +2 -2
- package/dist/{codex-session-runner-kh6s4j3n.js → codex-session-runner-02sb49e4.js} +5 -5
- package/dist/{commands-68et1qv2.js → commands-3rcspxct.js} +2 -2
- package/dist/{controller-fyrbnzg4.js → controller-8ar4avd6.js} +3 -3
- package/dist/{controllers-connect-nep9h2xv.js → controllers-connect-ajvh9h0v.js} +19 -19
- package/dist/{cursor-adapter-yss3pg7t.js → cursor-adapter-gd0sa7bh.js} +1 -1
- package/dist/{db-28gjyvxj.js → db-dv9j7ts5.js} +2 -2
- package/dist/{db-8ftwvzba.js → db-z26vgdvj.js} +3 -3
- package/dist/{dispatcher-hayexmkr.js → dispatcher-8m0htjq8.js} +17 -17
- package/dist/{dsh-adapter-46wawff9.js → dsh-adapter-y8gyk130.js} +1 -1
- package/dist/{e2b-fn3zacvt.js → e2b-74vg9b7e.js} +1 -1
- package/dist/{e2b-222hsajc.js → e2b-jyay8wzn.js} +1 -1
- package/dist/{easter-egg-3d-8d1e0h4m.js → easter-egg-3d-28f880xx.js} +1 -1
- package/dist/{execute-cjrfxfnm.js → execute-9seb79vh.js} +1 -1
- package/dist/{external-channel-xbmxcg09.js → external-channel-smz7jyth.js} +2 -2
- package/dist/{getMachineId-bsd-brn0xym3.js → getMachineId-bsd-whfre6gr.js} +3 -3
- package/dist/{getMachineId-darwin-t339sfxm.js → getMachineId-darwin-5zjynb19.js} +3 -3
- package/dist/{getMachineId-linux-5eq95d8b.js → getMachineId-linux-8mksrrsq.js} +3 -3
- package/dist/{getMachineId-unsupported-qavrtc7j.js → getMachineId-unsupported-fxtqsdt2.js} +3 -3
- package/dist/{getMachineId-win-q7fe1wx7.js → getMachineId-win-nzfmz857.js} +3 -3
- package/dist/{grok-adapter-ag4wks2a.js → grok-adapter-z0nnvtbn.js} +7 -7
- package/dist/handlers-pspryh6g.js +41 -0
- package/dist/{heartbeat-psfvcgwp.js → heartbeat-f928aqyp.js} +20 -20
- package/dist/{hook-cyq4mryn.js → hook-d7c605r4.js} +7 -7
- package/dist/{hook-8pz04697.js → hook-q9hv5xf9.js} +5 -5
- package/dist/{http-haj1nd6k.js → http-q6j4k989.js} +934 -180
- package/dist/{index-z5h22pes.js → index-5nb266jr.js} +17 -17
- package/dist/{index-ysd45kgc.js → index-8fxt5eah.js} +10 -10
- package/dist/{index-zgvqzjsw.js → index-cekzwhez.js} +5 -5
- package/dist/{index-zyq8ct52.js → index-ch0c1xff.js} +17 -17
- package/dist/{index-syvhnvtw.js → index-ebnjhxh1.js} +1 -1
- package/dist/{index-y1ajx04a.js → index-ed8dy8rn.js} +20 -20
- package/dist/{index-jwt2bj6f.js → index-ejqdkkqx.js} +11 -10
- package/dist/{index-dr94zbnh.js → index-farm1zsn.js} +32661 -29443
- package/dist/{index-q52f47pe.js → index-yn1yxg85.js} +1 -1
- package/dist/{keepalive-xdvsamvj.js → keepalive-zzc3hks1.js} +3 -3
- package/dist/{lead-hqg57kdp.js → lead-c0pjcaw4.js} +36 -36
- package/dist/{maintenance-4y2xzh3a.js → maintenance-kqsxzpzx.js} +1 -1
- package/dist/{oauth-refresh-sweep-jd4r7zbs.js → oauth-refresh-sweep-7qt0g55z.js} +2 -2
- package/dist/{onboard-18447q9q.js → onboard-k7x13fqg.js} +118 -5
- package/dist/{opencode-adapter-26b2frdf.js → opencode-adapter-tqvrtter.js} +5 -5
- package/dist/{otel-impl-f9tgr8yw.js → otel-impl-sqkf6k3b.js} +97 -97
- package/dist/{pi-mono-adapter-qn6qbga1.js → pi-mono-adapter-m2qnafxm.js} +63 -9
- package/dist/{pricing-refresh-dw2tgb53.js → pricing-refresh-wkpwhgvs.js} +3 -3
- package/dist/{rbac-roles-s4nwj2m3.js → rbac-roles-dpn0c0wj.js} +7 -4
- package/dist/{rbac-roles-aygm2760.js → rbac-roles-wk7xd6a6.js} +2 -2
- package/dist/{render-v2-yfvdfr0x.js → render-v2-ex2m4g2k.js} +3 -3
- package/dist/{resume-1bmvjbf3.js → resume-725g9b0b.js} +7 -7
- package/dist/{runtime-f7tpzedy.js → runtime-kajas15g.js} +1 -1
- package/dist/{session-access-r5garej6.js → session-access-0j2ezjje.js} +8 -4
- package/dist/{session-source-backfill-424wzv0r.js → session-source-backfill-62202awe.js} +2 -2
- package/dist/{setup-8tygvxkq.js → setup-9sj11ax6.js} +1 -1
- package/dist/{virtual-modules-xsw323b3.js → virtual-modules-c6fcq6zr.js} +2 -2
- package/dist/{work-objects-ggnghpgf.js → work-objects-0y07495y.js} +20 -20
- package/dist/{worker-sqjd1nqf.js → worker-d4q9sqbc.js} +36 -36
- package/openapi.json +1070 -9
- package/package.json +3 -3
- package/src/be/connector-codes.ts +65 -22
- package/src/be/memory/boot-reembed.ts +4 -1
- package/src/be/memory/constants.ts +6 -0
- package/src/be/memory/embedding-recovery.ts +48 -0
- package/src/be/memory/index-content.ts +5 -2
- package/src/be/memory/providers/openai-embedding.ts +65 -14
- package/src/be/memory/task-learning.ts +110 -8
- package/src/be/memory/types.ts +13 -2
- package/src/be/migrations/246_connector_code_kind.sql +11 -0
- package/src/be/onboarding.ts +125 -15
- package/src/be/rbac-roles.ts +4 -1
- package/src/be/scripts/embeddings.ts +2 -2
- package/src/be/seed/agent-fs-provision.ts +75 -14
- package/src/be/swarm-config-guard.ts +3 -0
- package/src/cli.tsx +9 -0
- package/src/comb/config.ts +6 -1
- package/src/commands/onboard/agent-fs-version.ts +5 -0
- package/src/commands/onboard/compose-generator.ts +112 -1
- package/src/commands/onboard/env-generator.ts +9 -0
- package/src/commands/onboard/tei-image.ts +12 -0
- package/src/commands/onboard/types.ts +7 -0
- package/src/commands/onboard.tsx +11 -1
- package/src/commands/runner.ts +14 -1
- package/src/http/agentfs-proxy.ts +369 -0
- package/src/http/all-routes.ts +1 -0
- package/src/http/codex-oauth-device.ts +8 -1
- package/src/http/fs.ts +64 -10
- package/src/http/inbox-state.ts +27 -1
- package/src/http/index.ts +4 -1
- package/src/http/memory.ts +7 -4
- package/src/http/onboarding.ts +139 -11
- package/src/http/session-access.ts +24 -0
- package/src/http/sessions.ts +10 -6
- package/src/http/tasks.ts +85 -11
- package/src/http/telemetry-ui.ts +1 -1
- package/src/http/user-roles.ts +4 -1
- package/src/http/users.ts +272 -3
- package/src/http/utils.ts +22 -3
- package/src/prompts/base-prompt.ts +24 -9
- package/src/prompts/session-templates.ts +26 -2
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +6 -1
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +58 -3
- package/src/rbac/legacy-policy.ts +1 -0
- package/src/rbac/permissions.ts +4 -0
- package/src/tests/agent-fs-provision-seeder.test.ts +261 -92
- package/src/tests/agentfs-proxy.test.ts +1106 -0
- package/src/tests/base-prompt.test.ts +69 -2
- package/src/tests/codex-adapter.test.ts +46 -8
- package/src/tests/codex-oauth-device.test.ts +12 -1
- package/src/tests/comb-config-guard.test.ts +20 -0
- package/src/tests/comb-links.test.ts +79 -1
- package/src/tests/comb-status.test.ts +44 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/fixtures/onboard/baseline-states.ts +68 -0
- package/src/tests/fixtures/onboard/content.compose.yml.txt +190 -0
- package/src/tests/fixtures/onboard/content.env.txt +29 -0
- package/src/tests/fixtures/onboard/dev.compose.yml.txt +155 -0
- package/src/tests/fixtures/onboard/dev.env.txt +28 -0
- package/src/tests/fixtures/onboard/full-all.compose.yml.txt +582 -0
- package/src/tests/fixtures/onboard/full-all.env.txt +59 -0
- package/src/tests/fixtures/onboard/full.compose.yml.txt +435 -0
- package/src/tests/fixtures/onboard/full.env.txt +36 -0
- package/src/tests/fixtures/onboard/research.compose.yml.txt +155 -0
- package/src/tests/fixtures/onboard/research.env.txt +28 -0
- package/src/tests/fixtures/onboard/solo.compose.yml.txt +85 -0
- package/src/tests/fixtures/onboard/solo.env.txt +26 -0
- package/src/tests/http-log-scrubbing.test.ts +34 -1
- package/src/tests/http-task-write-ownership.test.ts +5 -1
- package/src/tests/invites-http.test.ts +464 -0
- package/src/tests/memory-embedding-input-cap.test.ts +122 -1
- package/src/tests/memory-embedding-recovery.test.ts +137 -0
- package/src/tests/onboard-compose.test.ts +142 -0
- package/src/tests/onboard-env.test.ts +24 -0
- package/src/tests/onboard-non-interactive.test.ts +29 -0
- package/src/tests/onboard-tei-image.test.ts +36 -0
- package/src/tests/onboarding.test.ts +451 -4
- package/src/tests/openai-embedding-provider.test.ts +105 -2
- package/src/tests/pi-mono-session-cost-on-abort.test.ts +270 -0
- package/src/tests/preload-env.test.ts +4 -0
- package/src/tests/preload.ts +4 -0
- package/src/tests/prompt-template-session.test.ts +19 -3
- package/src/tests/rbac-admission-e2e.test.ts +22 -6
- package/src/tests/rbac-admission.test.ts +342 -4
- package/src/tests/rbac-engine.test.ts +1 -0
- package/src/tests/rbac-roles.test.ts +9 -1
- package/src/tests/send-task-requester-gate.test.ts +64 -3
- package/src/tests/sessions-move.test.ts +39 -1
- package/src/tests/store-progress-learning.test.ts +95 -2
- package/src/tests/whoami-route.test.ts +41 -0
- package/src/tools/inject-learning.ts +1 -1
- package/src/tools/memory-edit.ts +1 -1
- package/src/tools/memory-search.ts +1 -1
- package/src/tools/store-progress.ts +5 -1
- package/src/tools/utils.ts +12 -1
- package/src/utils/constants.ts +39 -0
- package/src/utils/task-attachment-links.ts +15 -3
- package/templates/skills/swarm-docs/content.md +1 -1
- package/dist/handlers-tzmevqb5.js +0 -41
- package/dist/{azure-openai-responses-9kfwhmn7.js → azure-openai-responses-43asa23s.js} +0 -0
- package/dist/{node-vxx896fa.js → node-t28cc56b.js} +0 -0
- package/dist/{openai-codex-responses-79s8xnbh.js → openai-codex-responses-jhr3sjtm.js} +3 -3
- package/dist/{openai-completions-tdbyts0x.js → openai-completions-gs6yn37k.js} +3 -3
- /package/dist/{openai-responses-dm3t5vqp.js → openai-responses-jqm9vwwk.js} +0 -0
package/src/http/onboarding.ts
CHANGED
|
@@ -2,12 +2,15 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
|
|
2
2
|
import OpenAI from "openai";
|
|
3
3
|
import { getAutomationSetupStates } from "../be/automation-preflight";
|
|
4
4
|
import { getAgentHarnessProviders, getReadyAgentCounts, getTaskById } from "../be/db";
|
|
5
|
-
import { getEmbeddingProvider } from "../be/memory";
|
|
5
|
+
import { getEmbeddingProvider, resetEmbeddingProvider } from "../be/memory";
|
|
6
6
|
import { EMBEDDING_DIMENSIONS } from "../be/memory/constants";
|
|
7
7
|
import {
|
|
8
|
+
EMBEDDING_PREFIX_KEYS,
|
|
9
|
+
type MemoryConfigChange,
|
|
8
10
|
OnboardingActionSchema,
|
|
9
11
|
type OnboardingErrorClass,
|
|
10
12
|
type OnboardingMemoryPreset,
|
|
13
|
+
type OnboardingMemoryProvider,
|
|
11
14
|
type OnboardingMemoryRequest,
|
|
12
15
|
OnboardingMemoryRequestSchema,
|
|
13
16
|
type OnboardingMemoryResponse,
|
|
@@ -21,16 +24,89 @@ import {
|
|
|
21
24
|
} from "../be/onboarding";
|
|
22
25
|
import { assertUrlSafe, publicEndpointSsrfOptions } from "../oauth/mcp-wrapper";
|
|
23
26
|
import { ProviderNameSchema } from "../types";
|
|
27
|
+
import { isCombEnabled } from "../utils/constants";
|
|
24
28
|
import { splitCredentialPool } from "../utils/credentials";
|
|
29
|
+
import { isPrivateAddress } from "../utils/egress-guard";
|
|
25
30
|
import { getOpenRouterAttributionHeaders } from "../utils/openrouter-base-url";
|
|
26
31
|
import { ensureConfigAdmin } from "./config";
|
|
27
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
getDeploymentEnvValue,
|
|
34
|
+
loadGlobalConfigsIntoEnv,
|
|
35
|
+
scheduleIntegrationsReload,
|
|
36
|
+
} from "./core";
|
|
28
37
|
import { route } from "./route-def";
|
|
29
38
|
import { rollupCredStatusForProvider } from "./status";
|
|
30
39
|
import { jsonError } from "./utils";
|
|
31
40
|
|
|
32
41
|
export type { OnboardingState } from "../be/onboarding";
|
|
33
42
|
|
|
43
|
+
/**
|
|
44
|
+
* The embeddings endpoint runs on this server or its private network: a
|
|
45
|
+
* loopback name, a private IP, or a single-label host such as the bundled
|
|
46
|
+
* `tei` service. Unset means the hosted OpenAI default.
|
|
47
|
+
*/
|
|
48
|
+
function isLocalEndpoint(baseUrl: string | undefined): boolean {
|
|
49
|
+
if (!baseUrl?.trim()) return false;
|
|
50
|
+
let host: string;
|
|
51
|
+
try {
|
|
52
|
+
host = new URL(baseUrl.trim()).hostname.toLowerCase().replace(/\.$/, "");
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
if (!host) return false;
|
|
57
|
+
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
58
|
+
if (isPrivateAddress(host)) return true;
|
|
59
|
+
// IPv6 literals hold colons: only a plain name can be a single label.
|
|
60
|
+
return !host.includes(".") && !host.includes(":");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The hosts of the hosted memory presets (the UI's preset list). */
|
|
64
|
+
const PRESET_HOSTS: ReadonlyArray<[OnboardingMemoryProvider, (host: string) => boolean]> = [
|
|
65
|
+
["openai", (host) => host === "api.openai.com"],
|
|
66
|
+
["openrouter", (host) => host === "openrouter.ai"],
|
|
67
|
+
["vercel", (host) => host === "ai-gateway.vercel.sh"],
|
|
68
|
+
[
|
|
69
|
+
"azure",
|
|
70
|
+
(host) =>
|
|
71
|
+
[".services.ai.azure.com", ".openai.azure.com", ".cognitiveservices.azure.com"].some(
|
|
72
|
+
(suffix) => host.endsWith(suffix),
|
|
73
|
+
),
|
|
74
|
+
],
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
/** Where an embeddings endpoint runs, from its host. Unset means the hosted OpenAI default. */
|
|
78
|
+
function embeddingsProvider(baseUrl: string | undefined): OnboardingMemoryProvider {
|
|
79
|
+
if (isLocalEndpoint(baseUrl)) return "local";
|
|
80
|
+
if (!baseUrl?.trim()) return "openai";
|
|
81
|
+
let host: string;
|
|
82
|
+
try {
|
|
83
|
+
host = new URL(baseUrl.trim()).hostname.toLowerCase().replace(/\.$/, "");
|
|
84
|
+
} catch {
|
|
85
|
+
return "custom";
|
|
86
|
+
}
|
|
87
|
+
return PRESET_HOSTS.find(([, matches]) => matches(host))?.[0] ?? "custom";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The deployment environment's own embeddings setup, before a stored config
|
|
92
|
+
* replaced it: its base URL, model, and key. The key can also be a stored
|
|
93
|
+
* OPENAI_API_KEY, as for the setup the server runs.
|
|
94
|
+
*/
|
|
95
|
+
function deploymentEmbeddings() {
|
|
96
|
+
return {
|
|
97
|
+
baseUrl: getDeploymentEnvValue("EMBEDDING_API_BASE_URL"),
|
|
98
|
+
model: getDeploymentEnvValue("EMBEDDING_MODEL") ?? "text-embedding-3-small",
|
|
99
|
+
apiKey: getDeploymentEnvValue("EMBEDDING_API_KEY") ?? process.env.OPENAI_API_KEY,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** The local setup of the deployment environment, when it has a local endpoint and a key. */
|
|
104
|
+
function localDefault(): { model: string } | null {
|
|
105
|
+
const deployment = deploymentEmbeddings();
|
|
106
|
+
if (!isLocalEndpoint(deployment.baseUrl) || !deployment.apiKey) return null;
|
|
107
|
+
return { model: deployment.model };
|
|
108
|
+
}
|
|
109
|
+
|
|
34
110
|
async function buildSignals(state: OnboardingState): Promise<OnboardingSignals> {
|
|
35
111
|
const providers: OnboardingSignals["providers"] = [];
|
|
36
112
|
for (const entry of await getAgentHarnessProviders()) {
|
|
@@ -51,12 +127,18 @@ async function buildSignals(state: OnboardingState): Promise<OnboardingSignals>
|
|
|
51
127
|
const integrations = await getAutomationSetupStates();
|
|
52
128
|
const agentCounts = await getReadyAgentCounts();
|
|
53
129
|
const firstTask = state.firstTaskId ? await getTaskById(state.firstTaskId) : null;
|
|
130
|
+
const embeddingsOn = getEmbeddingProvider().isConfigured();
|
|
54
131
|
|
|
55
132
|
return {
|
|
56
133
|
providers,
|
|
57
134
|
embeddings: {
|
|
58
|
-
configured:
|
|
135
|
+
configured: embeddingsOn,
|
|
59
136
|
dimensions: EMBEDDING_DIMENSIONS,
|
|
137
|
+
// The model id and the host class only: never the key or the URL.
|
|
138
|
+
model: embeddingsOn ? (process.env.EMBEDDING_MODEL ?? "text-embedding-3-small") : null,
|
|
139
|
+
local: embeddingsOn && isLocalEndpoint(process.env.EMBEDDING_API_BASE_URL),
|
|
140
|
+
provider: embeddingsOn ? embeddingsProvider(process.env.EMBEDDING_API_BASE_URL) : null,
|
|
141
|
+
localDefault: localDefault(),
|
|
60
142
|
},
|
|
61
143
|
integrations: {
|
|
62
144
|
slack: integrations.slack === "configured",
|
|
@@ -71,6 +153,7 @@ async function buildSignals(state: OnboardingState): Promise<OnboardingSignals>
|
|
|
71
153
|
workersOnline: agentCounts.workers,
|
|
72
154
|
},
|
|
73
155
|
firstTask: firstTask ? { id: firstTask.id, status: firstTask.status } : null,
|
|
156
|
+
files: { enabled: isCombEnabled() },
|
|
74
157
|
};
|
|
75
158
|
}
|
|
76
159
|
|
|
@@ -195,10 +278,31 @@ function memoryErrorMessage(errorClass: OnboardingErrorClass, status?: number):
|
|
|
195
278
|
async function updateMemoryState(
|
|
196
279
|
preset: OnboardingMemoryPreset,
|
|
197
280
|
result: { ok: true } | { ok: false; errorClass: OnboardingErrorClass },
|
|
198
|
-
|
|
281
|
+
change?: MemoryConfigChange,
|
|
199
282
|
): Promise<void> {
|
|
200
|
-
const
|
|
201
|
-
if (
|
|
283
|
+
const configChanged = await updateOnboardingMemory(preset, result, change);
|
|
284
|
+
if (!configChanged) return;
|
|
285
|
+
if (change?.kind === "restore") {
|
|
286
|
+
// Apply the deployment setup before the response: the next GET shows it
|
|
287
|
+
// in use. Memory search stays on, so the reload starts no backfill.
|
|
288
|
+
await loadGlobalConfigsIntoEnv(true);
|
|
289
|
+
resetEmbeddingProvider();
|
|
290
|
+
}
|
|
291
|
+
scheduleIntegrationsReload();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* The task prefixes of a saved setup. A prefix of the deployment environment
|
|
296
|
+
* belongs to its model (nomic needs `search_query: `): another model gets an
|
|
297
|
+
* empty stored prefix over it, and the same model gets the deployment prefix
|
|
298
|
+
* back (the stored row is deleted).
|
|
299
|
+
*/
|
|
300
|
+
function prefixChanges(model: string): Extract<MemoryConfigChange, { kind: "save" }>["prefixes"] {
|
|
301
|
+
const sameModel = model === getDeploymentEnvValue("EMBEDDING_MODEL");
|
|
302
|
+
return EMBEDDING_PREFIX_KEYS.filter((key) => getDeploymentEnvValue(key)).map((key) => ({
|
|
303
|
+
key,
|
|
304
|
+
value: sameModel ? null : "",
|
|
305
|
+
}));
|
|
202
306
|
}
|
|
203
307
|
|
|
204
308
|
async function handleMemoryProbe(body: OnboardingMemoryRequest): Promise<OnboardingMemoryResponse> {
|
|
@@ -207,21 +311,39 @@ async function handleMemoryProbe(body: OnboardingMemoryRequest): Promise<Onboard
|
|
|
207
311
|
body.preset === "openai" || body.preset === "openrouter" || body.preset === "vercel"
|
|
208
312
|
? MEMORY_PRESETS[body.preset]
|
|
209
313
|
: undefined;
|
|
314
|
+
// `existing` tests the setup the server runs. With `restoreDefault` it
|
|
315
|
+
// tests the deployment environment's setup, which applies once the stored
|
|
316
|
+
// config is gone.
|
|
317
|
+
const restore = body.preset === "existing" && body.restoreDefault === true;
|
|
318
|
+
const deployment = restore ? deploymentEmbeddings() : null;
|
|
210
319
|
const baseUrl =
|
|
211
320
|
body.preset === "existing"
|
|
212
|
-
?
|
|
321
|
+
? deployment
|
|
322
|
+
? deployment.baseUrl
|
|
323
|
+
: process.env.EMBEDDING_API_BASE_URL
|
|
213
324
|
: (body.baseUrl ??
|
|
214
325
|
preset?.baseUrl ??
|
|
215
326
|
process.env.EMBEDDING_API_BASE_URL ??
|
|
216
327
|
"https://api.openai.com/v1");
|
|
217
328
|
const model =
|
|
218
329
|
body.preset === "existing"
|
|
219
|
-
? (process.env.EMBEDDING_MODEL ?? "text-embedding-3-small")
|
|
330
|
+
? (deployment?.model ?? process.env.EMBEDDING_MODEL ?? "text-embedding-3-small")
|
|
220
331
|
: (body.model ?? preset?.model ?? process.env.EMBEDDING_MODEL ?? "text-embedding-3-small");
|
|
221
332
|
|
|
333
|
+
// Preset `existing` tests the server's own EMBEDDING_API_BASE_URL. When it is
|
|
334
|
+
// the deployment environment's value, the URL is operator config, not request
|
|
335
|
+
// input, so a private endpoint (a local TEI at http://tei:80/v1) is allowed. A
|
|
336
|
+
// value stored through `/api/config` and every URL that can come from the
|
|
337
|
+
// request still pass the SSRF check.
|
|
338
|
+
const configuredEndpoint =
|
|
339
|
+
body.preset === "existing" &&
|
|
340
|
+
!!baseUrl &&
|
|
341
|
+
baseUrl === getDeploymentEnvValue("EMBEDDING_API_BASE_URL");
|
|
222
342
|
let endpoint: URL;
|
|
223
343
|
try {
|
|
224
|
-
endpoint =
|
|
344
|
+
endpoint = configuredEndpoint
|
|
345
|
+
? new URL(baseUrl ?? "")
|
|
346
|
+
: assertUrlSafe(baseUrl ?? "", publicEndpointSsrfOptions());
|
|
225
347
|
} catch {
|
|
226
348
|
// Input rejections are not probe outcomes: the step state stays as is.
|
|
227
349
|
return {
|
|
@@ -243,7 +365,9 @@ async function handleMemoryProbe(body: OnboardingMemoryRequest): Promise<Onboard
|
|
|
243
365
|
!explicitKey && body.reuseKey && canReuseKey ? process.env[body.reuseKey] : undefined;
|
|
244
366
|
const implicitKey =
|
|
245
367
|
body.preset === "existing"
|
|
246
|
-
?
|
|
368
|
+
? deployment
|
|
369
|
+
? deployment.apiKey
|
|
370
|
+
: (process.env.EMBEDDING_API_KEY ?? process.env.OPENAI_API_KEY)
|
|
247
371
|
: undefined;
|
|
248
372
|
// Test and save the same pool member.
|
|
249
373
|
const apiKey = splitCredentialPool(explicitKey ?? reusedKey ?? implicitKey ?? "")[0];
|
|
@@ -290,11 +414,15 @@ async function handleMemoryProbe(body: OnboardingMemoryRequest): Promise<Onboard
|
|
|
290
414
|
body.preset,
|
|
291
415
|
{ ok: true },
|
|
292
416
|
body.preset === "existing"
|
|
293
|
-
?
|
|
417
|
+
? restore
|
|
418
|
+
? { kind: "restore" }
|
|
419
|
+
: undefined
|
|
294
420
|
: {
|
|
421
|
+
kind: "save",
|
|
295
422
|
baseUrl: baseUrl ?? "https://api.openai.com/v1",
|
|
296
423
|
model,
|
|
297
424
|
apiKey: keyToSave,
|
|
425
|
+
prefixes: prefixChanges(model),
|
|
298
426
|
},
|
|
299
427
|
);
|
|
300
428
|
return { ok: true, dimensions, latencyMs };
|
|
@@ -88,6 +88,30 @@ export async function hasSessionWrite(
|
|
|
88
88
|
return hasSessionAccess(access, "write");
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
export const NOT_YOUR_TASK_DENIED =
|
|
92
|
+
"You can only change tasks you requested, or sessions shared with you to edit.";
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* True when a user token without a grant-all role (RBAC on) changes a task it
|
|
96
|
+
* does not own: neither the task nor its session root names the user as
|
|
97
|
+
* requester, and the session is not shared with the user for writing. The
|
|
98
|
+
* session wall reads an ownerless root (Slack, schedules, operator key) as
|
|
99
|
+
* everyone's; for such a user it is not theirs to change. Operator, agent and
|
|
100
|
+
* admin callers are never denied here.
|
|
101
|
+
*/
|
|
102
|
+
export async function deniesNonOwnerTaskWrite(
|
|
103
|
+
req: IncomingMessage,
|
|
104
|
+
task: { id: string; requestedByUserId?: string | null },
|
|
105
|
+
): Promise<boolean> {
|
|
106
|
+
const auth = getRequestAuth(req);
|
|
107
|
+
if (auth?.kind !== "user" || !isRbacEnabled()) return false;
|
|
108
|
+
if (task.requestedByUserId === auth.userId) return false;
|
|
109
|
+
if ((await getUserGrant(auth.userId)).grantsAll) return false;
|
|
110
|
+
const root = await getSessionRoot(task.id);
|
|
111
|
+
if (!root?.requestedByUserId) return true;
|
|
112
|
+
return !hasSessionAccess(await sessionAccessForRoot(auth.userId, root), "write");
|
|
113
|
+
}
|
|
114
|
+
|
|
91
115
|
/**
|
|
92
116
|
* The parent gate shared by `POST /api/tasks` and `POST /api/apps/{id}/actions/{name}`.
|
|
93
117
|
* The child inherits the parent's thread context, so an agent caller must own or
|
package/src/http/sessions.ts
CHANGED
|
@@ -77,7 +77,13 @@ import { isRuntimePoolsEnabled } from "../utils/runtime-pools-flag";
|
|
|
77
77
|
import { canMutateTaskNamespace } from "./assets";
|
|
78
78
|
import { agentFirstPrincipal } from "./request-principal";
|
|
79
79
|
import { route } from "./route-def";
|
|
80
|
-
import {
|
|
80
|
+
import {
|
|
81
|
+
deniesNonOwnerTaskWrite,
|
|
82
|
+
NOT_YOUR_TASK_DENIED,
|
|
83
|
+
resolveSessionActor,
|
|
84
|
+
type SessionActor,
|
|
85
|
+
sessionAccessFor,
|
|
86
|
+
} from "./session-access";
|
|
81
87
|
import { jsonError } from "./utils";
|
|
82
88
|
|
|
83
89
|
// ─── Response Schemas ────────────────────────────────────────────────────────
|
|
@@ -660,7 +666,7 @@ const triageSessionListRoute = route({
|
|
|
660
666
|
401: { description: "Unauthorized" },
|
|
661
667
|
},
|
|
662
668
|
auth: { apiKey: true },
|
|
663
|
-
rbac: {
|
|
669
|
+
rbac: { permission: "favorite.write.own" },
|
|
664
670
|
});
|
|
665
671
|
|
|
666
672
|
const SESSION_MOVE_LIMIT = 200;
|
|
@@ -671,10 +677,7 @@ const SessionMoveResultSchema = z.object({
|
|
|
671
677
|
moved: z.array(z.string()),
|
|
672
678
|
skipped: z.array(z.object({ rootTaskId: z.string(), reason: z.string() })),
|
|
673
679
|
});
|
|
674
|
-
const sessionMoveRbac = {
|
|
675
|
-
ungated:
|
|
676
|
-
"per-session access, per-task task.fs.mutate, and asset-key write checks in the handler, as PATCH /api/assets/task/{id}/key",
|
|
677
|
-
};
|
|
680
|
+
const sessionMoveRbac = { permission: "task.fs.mutate" } as const;
|
|
678
681
|
const sessionMoveResponses = {
|
|
679
682
|
400: { description: "Invalid namespace or request" },
|
|
680
683
|
403: { description: "Personal destination not authorized" },
|
|
@@ -759,6 +762,7 @@ async function checkSessionMove(
|
|
|
759
762
|
const access = await sessionAccessFor(actor, root.id);
|
|
760
763
|
if (access === "none") return { reason: SESSION_MOVE_UNAVAILABLE };
|
|
761
764
|
if (!hasSessionAccess(access, "write")) return { reason: "Session write access required" };
|
|
765
|
+
if (await deniesNonOwnerTaskWrite(req, root)) return { reason: NOT_YOUR_TASK_DENIED };
|
|
762
766
|
}
|
|
763
767
|
if (root.parentTaskId) return { reason: "Task is not a session root" };
|
|
764
768
|
const sourceError = sourceMoveError(options.destination ?? null, root);
|
package/src/http/tasks.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getAgentById,
|
|
12
12
|
getAllTasks,
|
|
13
13
|
getDbClient,
|
|
14
|
+
getInProgressTasksByContextKey,
|
|
14
15
|
getLeadAgent,
|
|
15
16
|
getLogsByTaskId,
|
|
16
17
|
getPausedTasksForAgent,
|
|
@@ -39,6 +40,7 @@ import {
|
|
|
39
40
|
} from "../be/db";
|
|
40
41
|
import { modelProfileRefError, taskProfileError } from "../be/db/model-profiles";
|
|
41
42
|
import { explicitModelErrorForAgent } from "../be/model-validation";
|
|
43
|
+
import { getUserGrant } from "../be/rbac-roles";
|
|
42
44
|
import { hasSessionAccess, type SessionAccess } from "../be/session-shares";
|
|
43
45
|
import {
|
|
44
46
|
getTaskSteeringFields,
|
|
@@ -49,7 +51,7 @@ import {
|
|
|
49
51
|
} from "../be/steering";
|
|
50
52
|
import { getTaskCitations, TaskCitationSchema } from "../be/task-citations";
|
|
51
53
|
import { findUserById } from "../be/users";
|
|
52
|
-
import { can, type RbacPrincipal, type RbacResource } from "../rbac";
|
|
54
|
+
import { can, isRbacEnabled, type RbacPrincipal, type RbacResource } from "../rbac";
|
|
53
55
|
import { chatThreadOfContextKey } from "../tasks/context-key";
|
|
54
56
|
import { TaskCreationBlockedError } from "../tasks/errors";
|
|
55
57
|
import { createTaskWithSiblingAwareness } from "../tasks/sibling-awareness";
|
|
@@ -87,7 +89,9 @@ import { buildTaskRequestedBy, TaskRequestedBySchema } from "./requested-by";
|
|
|
87
89
|
import { bodyTask, route } from "./route-def";
|
|
88
90
|
import {
|
|
89
91
|
canReadSession,
|
|
92
|
+
deniesNonOwnerTaskWrite,
|
|
90
93
|
denyParentAttach,
|
|
94
|
+
NOT_YOUR_TASK_DENIED,
|
|
91
95
|
readableByFor,
|
|
92
96
|
resolveSessionActor,
|
|
93
97
|
SESSION_READ_DENIED,
|
|
@@ -279,6 +283,7 @@ const createTask = route({
|
|
|
279
283
|
path: "/api/tasks",
|
|
280
284
|
pattern: ["api", "tasks"],
|
|
281
285
|
session: bodyTask("parentTaskId"),
|
|
286
|
+
rbac: { permission: "task.create.own" },
|
|
282
287
|
summary: "Create a new task",
|
|
283
288
|
tags: ["Tasks"],
|
|
284
289
|
body: z
|
|
@@ -411,6 +416,7 @@ const cancelTaskRoute = route({
|
|
|
411
416
|
path: "/api/tasks/{id}/cancel",
|
|
412
417
|
pattern: ["api", "tasks", null, "cancel"],
|
|
413
418
|
session: { taskParam: "id" },
|
|
419
|
+
rbac: { permission: "task.cancel.own" },
|
|
414
420
|
summary: "Cancel a pending or in-progress task",
|
|
415
421
|
tags: ["Tasks"],
|
|
416
422
|
params: z.object({ id: z.string() }),
|
|
@@ -687,6 +693,7 @@ const pauseTaskRoute = route({
|
|
|
687
693
|
path: "/api/tasks/{id}/pause",
|
|
688
694
|
pattern: ["api", "tasks", null, "pause"],
|
|
689
695
|
session: { taskParam: "id" },
|
|
696
|
+
rbac: { permission: "task.action.own" },
|
|
690
697
|
summary: "Pause an in-progress task",
|
|
691
698
|
tags: ["Tasks"],
|
|
692
699
|
params: z.object({ id: z.string() }),
|
|
@@ -703,6 +710,7 @@ const resumeTaskRoute = route({
|
|
|
703
710
|
path: "/api/tasks/{id}/resume",
|
|
704
711
|
pattern: ["api", "tasks", null, "resume"],
|
|
705
712
|
session: { taskParam: "id" },
|
|
713
|
+
rbac: { permission: "task.action.own" },
|
|
706
714
|
summary: "Resume a paused task",
|
|
707
715
|
tags: ["Tasks"],
|
|
708
716
|
params: z.object({ id: z.string() }),
|
|
@@ -777,9 +785,7 @@ const updateTaskTitleRoute = route({
|
|
|
777
785
|
404: { description: "Task not found" },
|
|
778
786
|
},
|
|
779
787
|
auth: { apiKey: true },
|
|
780
|
-
rbac: {
|
|
781
|
-
ungated: "mirrors the pre-RBAC PATCH /api/tasks/{id}/vcs sibling route: bearer auth only",
|
|
782
|
-
},
|
|
788
|
+
rbac: { permission: "task.action.own" },
|
|
783
789
|
});
|
|
784
790
|
|
|
785
791
|
async function canSteerTask(
|
|
@@ -879,6 +885,40 @@ async function resolveTaskWritePrincipal(
|
|
|
879
885
|
return { kind: "agent", agentId, isLead: agent?.isLead === true };
|
|
880
886
|
}
|
|
881
887
|
|
|
888
|
+
/**
|
|
889
|
+
* May this caller bind a new task to a chat thread? The operator may. An agent needs the
|
|
890
|
+
* lead's chat post permission. A user needs admin, or a role with `integration.chat.post`.
|
|
891
|
+
*/
|
|
892
|
+
async function mayBindChatThread(principal: RbacPrincipal): Promise<boolean> {
|
|
893
|
+
if (principal.kind === "operator") return true;
|
|
894
|
+
if (principal.kind === "user") {
|
|
895
|
+
if (!isRbacEnabled()) return true;
|
|
896
|
+
const grant = await getUserGrant(principal.userId);
|
|
897
|
+
return grant.grantsAll || grant.verbs.has("integration.chat.post");
|
|
898
|
+
}
|
|
899
|
+
return can({
|
|
900
|
+
principal,
|
|
901
|
+
verb: "integration.chat.post",
|
|
902
|
+
resource: { kind: "none" },
|
|
903
|
+
source: "http",
|
|
904
|
+
}).allow;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Sibling awareness copies the open tasks under a contextKey into the new task, and may pick
|
|
909
|
+
* one as its parent. A non-admin user may name a context only when they may write each of
|
|
910
|
+
* its open tasks.
|
|
911
|
+
*/
|
|
912
|
+
async function deniesForeignContext(req: IncomingMessage, contextKey: string): Promise<boolean> {
|
|
913
|
+
const auth = getRequestAuth(req);
|
|
914
|
+
if (auth?.kind !== "user" || !isRbacEnabled()) return false;
|
|
915
|
+
if ((await getUserGrant(auth.userId)).grantsAll) return false;
|
|
916
|
+
for (const task of await getInProgressTasksByContextKey(contextKey)) {
|
|
917
|
+
if (await deniesNonOwnerTaskWrite(req, task)) return true;
|
|
918
|
+
}
|
|
919
|
+
return false;
|
|
920
|
+
}
|
|
921
|
+
|
|
882
922
|
/**
|
|
883
923
|
* Requester for a new task. Prefer trusted server-side identity: an
|
|
884
924
|
* authenticated request user, or the caller's own ownership-gated task context
|
|
@@ -1070,17 +1110,27 @@ export async function handleTasks(
|
|
|
1070
1110
|
return true;
|
|
1071
1111
|
}
|
|
1072
1112
|
// A chat-sdk contextKey binds the task to a chat thread that its assignee and creator
|
|
1073
|
-
// may then read and post to.
|
|
1074
|
-
//
|
|
1113
|
+
// may then read and post to. Only a caller with chat post permission may name a thread
|
|
1114
|
+
// other than the parent's.
|
|
1075
1115
|
if (
|
|
1076
|
-
principal.kind === "agent" &&
|
|
1077
1116
|
parsed.body.contextKey &&
|
|
1078
1117
|
parsed.body.contextKey !== parentTask?.contextKey &&
|
|
1079
1118
|
chatThreadOfContextKey(parsed.body.contextKey) &&
|
|
1080
|
-
!
|
|
1081
|
-
.allow
|
|
1119
|
+
!(await mayBindChatThread(principal))
|
|
1082
1120
|
) {
|
|
1083
|
-
jsonError(
|
|
1121
|
+
jsonError(
|
|
1122
|
+
res,
|
|
1123
|
+
"Permission denied: only a lead or an admin can bind a task to a chat thread.",
|
|
1124
|
+
403,
|
|
1125
|
+
);
|
|
1126
|
+
return true;
|
|
1127
|
+
}
|
|
1128
|
+
if (
|
|
1129
|
+
parsed.body.contextKey &&
|
|
1130
|
+
parsed.body.contextKey !== parentTask?.contextKey &&
|
|
1131
|
+
(await deniesForeignContext(req, parsed.body.contextKey))
|
|
1132
|
+
) {
|
|
1133
|
+
jsonError(res, "You don't have access to the tasks in this context.", 403);
|
|
1084
1134
|
return true;
|
|
1085
1135
|
}
|
|
1086
1136
|
let assetKey: string | undefined;
|
|
@@ -1240,7 +1290,12 @@ export async function handleTasks(
|
|
|
1240
1290
|
return true;
|
|
1241
1291
|
}
|
|
1242
1292
|
|
|
1243
|
-
|
|
1293
|
+
if (await deniesNonOwnerTaskWrite(req, task)) {
|
|
1294
|
+
jsonError(res, NOT_YOUR_TASK_DENIED, 403);
|
|
1295
|
+
return true;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// Admins may cancel any task. Agents get the MCP cancel-task policy:
|
|
1244
1299
|
// lead or task creator.
|
|
1245
1300
|
const principal = await resolveTaskWritePrincipal(req, myAgentId);
|
|
1246
1301
|
if (
|
|
@@ -1882,6 +1937,11 @@ export async function handleTasks(
|
|
|
1882
1937
|
return true;
|
|
1883
1938
|
}
|
|
1884
1939
|
|
|
1940
|
+
if (await deniesNonOwnerTaskWrite(req, task)) {
|
|
1941
|
+
jsonError(res, NOT_YOUR_TASK_DENIED, 403);
|
|
1942
|
+
return true;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1885
1945
|
if (myAgentId && task.agentId !== myAgentId) {
|
|
1886
1946
|
jsonError(res, "Task belongs to another agent", 403);
|
|
1887
1947
|
return true;
|
|
@@ -1945,6 +2005,15 @@ export async function handleTasks(
|
|
|
1945
2005
|
if (updateTaskTitleRoute.match(req.method, pathSegments)) {
|
|
1946
2006
|
const parsed = await updateTaskTitleRoute.parse(req, res, pathSegments, queryParams);
|
|
1947
2007
|
if (!parsed) return true;
|
|
2008
|
+
const existing = await getTaskById(parsed.params.id);
|
|
2009
|
+
if (!existing) {
|
|
2010
|
+
jsonError(res, "Task not found", 404);
|
|
2011
|
+
return true;
|
|
2012
|
+
}
|
|
2013
|
+
if (await deniesNonOwnerTaskWrite(req, existing)) {
|
|
2014
|
+
jsonError(res, NOT_YOUR_TASK_DENIED, 403);
|
|
2015
|
+
return true;
|
|
2016
|
+
}
|
|
1948
2017
|
const task = await updateTaskTitle(parsed.params.id, parsed.body.title);
|
|
1949
2018
|
if (!task) {
|
|
1950
2019
|
jsonError(res, "Task not found", 404);
|
|
@@ -1964,6 +2033,11 @@ export async function handleTasks(
|
|
|
1964
2033
|
return true;
|
|
1965
2034
|
}
|
|
1966
2035
|
|
|
2036
|
+
if (await deniesNonOwnerTaskWrite(req, task)) {
|
|
2037
|
+
jsonError(res, NOT_YOUR_TASK_DENIED, 403);
|
|
2038
|
+
return true;
|
|
2039
|
+
}
|
|
2040
|
+
|
|
1967
2041
|
if (myAgentId && task.agentId !== myAgentId) {
|
|
1968
2042
|
jsonError(res, "Task belongs to another agent", 403);
|
|
1969
2043
|
return true;
|
package/src/http/telemetry-ui.ts
CHANGED
|
@@ -32,7 +32,7 @@ const uiEvent = route({
|
|
|
32
32
|
401: { description: "Unauthorized" },
|
|
33
33
|
403: { description: "Guest page sessions cannot send events" },
|
|
34
34
|
},
|
|
35
|
-
rbac: {
|
|
35
|
+
rbac: { permission: "favorite.write.own" },
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
const enableTelemetry = route({
|
package/src/http/user-roles.ts
CHANGED
|
@@ -80,7 +80,10 @@ async function composeRoles(userId: string) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/** Operator or user only; agents never change human roles, even with the shared key. */
|
|
83
|
-
async function ensureRoleManager(
|
|
83
|
+
export async function ensureRoleManager(
|
|
84
|
+
req: IncomingMessage,
|
|
85
|
+
res: ServerResponse,
|
|
86
|
+
): Promise<boolean> {
|
|
84
87
|
if (rejectGuest(req, res)) return false;
|
|
85
88
|
const auth = getRequestAuth(req);
|
|
86
89
|
if (auth?.kind !== "operator" && auth?.kind !== "user") {
|