Skip to Content
操作流程任務、分享與啟用

任務、分享與啟用

建立與管理目錄任務走 owner scope。房間啟用走 目標聊天室管理者階梯。這不是「一律 company manager」。受眾規則只寫在一處:任務受眾。受限 API 金鑰在 tasks/shares/granted-jobs/bindings 一律 404。

步驟

  1. 建立任務 — POST /tasks(SandboxTaskCreateRequest):
    • name 1–256,description ≤2048。
    • owner_scope:只有 department 或 company。chatroom → 422。
    • owner_id:部門 id 或公司 id(company 必須等於 company_id)。
    • agent_enabled(預設 false)是舊旗標。Agent 能否看到任務看房間啟用開關。
  2. 建立草稿版本 — POST /tasks/{task_id}/versions:
    • environment_version_id:一個 ready 的環境版本(自家或策展)。
    • startup_script/work_script(各 ≤1 MiB)。
    • ordinary_env:名稱數與 secret slot 合計 ≤100;每個 NAME=VALUE 條目 ≤64 KiB,所有 ordinary 條目加總也只有 64 KiB(ordinary+secret 合計 ≤256 KiB);名稱須符合 ^[A-Za-z_][A-Za-z0-9_]{0,127}$,且不能是保留名(TEAMSYNC_、AWS_、PATH…)。注意:草稿建立/PATCH 與發布完全不檢查這些限制——超標的版本照樣發布;它們只在 secret claim/manifest reveal 時被強制,而且只對有宣告 secret slot 的版本生效——沒宣告 slot 的超標版本會一路通過後端不被擋。
    • input_instructions/input_example(≤64 KiB)。
    • input_schema(≤64 KiB,可選):JSON Schema(draft 2020-12),驗證每次 run 的 input。設定後,input_example 必須符合它(就在這次草稿建立/更新呼叫時檢查,不符合是 422 job_contract_invalid——發布不會重新驗證),而提交的 run 若 input 違反它,會在派工前被拒絕,回 422 input_schema_violation。
    • work_command(≤4096 UTF-8 位元組,可選):work 步驟的 driver 指令列,例如 python3 /workspace/work.sh——work_script 永遠落地在 /workspace/work.sh,上傳檔案的原始檔名不會保留,所以指令要指向那個固定路徑,不是你上傳時的檔名(沒有 work.py 這種檔)。Runner 會把它原封不動寫進 /workspace/driver.sh,在 startup.sh 之後 source 它(絕不是 argv)。空白/省略 → . /workspace/work.sh(此時 work_script 必須是 shell;填了直譯器就可以是任何語言)。含 NUL 或超長一樣是 422 job_contract_invalid。見下面的腳本檔案與 runtime 契約。
    • task_bundle_upload_id(可選):任務包——先用 POST /tasks/{task_id}/bundle-uploads(multipart 欄位 file,zip / tar / tar.gz,壓縮後 ≤20 MiB)上傳的封存檔。工作步驟開始前會解壓到 /workspace,所以一個任務可以帶任意多個檔案(程式、資料、設定),以絕對路徑讀取。見下方任務包。
    • timeout_seconds(預設 1800,ge=1,le≈7 天,也受公司 ceiling 限制)。
    • secret_slot_declarations:{ "name": "SLOT" } 或純字串。見 Secrets。
    • requires_confirmation(預設 false)。自訂表格 trigger 不能對準 true。
    • output_policy(可選,有型別,以 kind 區分的聯集):表格 writeback { "kind": "custom_table_writeback", "table_id": "...", "allowed_ops": "create" | "create,update" },或 Command 輸出 { "kind": "custom_table_command", "command_id": "...", "chatroom_id": "...", "input_schema": [ … ] }(v5.21.0——見任務輸出交給自訂表格 Command)。只允許部門擁有的任務。公司擁有的任務發布 → 409 output_policy_owner_scope_unsupported。不是不透明 JSON。
  3. 改草稿(可選)— PATCH /tasks/{task_id}/versions/{vid}(只有草稿)。跟建立同一組欄位,含 input_schema/work_command。
  4. 發布 — POST /tasks/{task_id}/versions/{vid}/publish → state: "published"。需要已發布版本的是啟用與執行,不是分享:分享是任務層級的(POST /tasks/{task_id}/shares 不帶版本,任務連一個已發布版本都還沒有也能先分享);啟用一定釘一個 published 且 content active 的版本;執行時版本非 published/active → 409 version_not_runnable(已 scrub 的版本則是 409 content_scrubbed_irreversible)。
  5. 分享 — POST /tasks/{task_id}/shares(target_kind + target_id,可選逐 slot 的 secret_slot_policies——見 Secrets)。見 分享對象。撤銷:.../shares/{grant_id}/revoke。分享不是 Agent 啟用。
  6. 房間啟用 — POST /chatrooms/{chatroom_id}/granted-jobs/{task_id}/enable(可選 task_version_id;省略 → 目前已發布版)。釘版本並放進 GET .../menu。當 live binding 已經釘在你要的版本(或你沒送 task_version_id)時,enable 是冪等的。送不同的 task_version_id 會重新釘版(後端 ≥ #1140):既有 binding 會像 disable 一樣被撤銷(舊版本上排隊中的 run 會被取消),再建立新的 binding;回應的 pinned_task_version_id 一定是你要求的版本。POST /bindings/{binding_id}/accept-version 仍是原地升版的方式。若任務沒有分享給這個聊天室(或其部門/公司),enable 會回 409 task_not_shared_to_room,訊息會寫出該呼叫哪條分享 API(#1140 之前是單純的 404)。停用:.../disable(share 還在)。

POST /bindings 是同一個釘選,只是必須帶明確版本。產品路徑請用 granted-jobs。

任務包(多檔案任務)

POST /tasks/{task_id}/bundle-uploads # multipart:file=<job.zip>(zip / tar / tar.gz,壓縮後 ≤20 MiB) # → { id, filename, archive_format, compressed_byte_size, extracted_byte_size, canonical_byte_size, file_count, sha256, manifest_sha256, expires_at } POST /tasks/{task_id}/versions # { environment_version_id, task_bundle_upload_id: <id>, work_script | work_command, ... }
  • 封存檔根目錄就是 /workspace:zip 裡的 app/main.py 在執行期是 /workspace/app/main.py。一律用絕對路徑讀取包內檔案。
  • 保留的根目錄檔名:startup.sh、work.sh、driver.sh、input.json 屬於 runner。封存檔根目錄含有其中任一個會被拒絕:422 bundle_path_forbidden,訊息會寫出是哪個檔案(runner-reserved root path: work.sh (rename it; …),後端 ≥ #1140)。把進入點腳本放到子目錄(例如 app/run.sh)再用 work_command 指過去,或把進入點寫在 inline 的 work_script。
  • 已發布版本帶 task_bundle 摘要(sha256、manifest_sha256、file_count、各種位元組大小);上傳本身若沒有版本採用會在 expires_at 後過期。
  • 可用的模式(2026-09-03 實機驗證):一個環境裝好各 runtime,三個任務掛在同一環境版本上,每個任務包 = app/<程式> + data/params.json,work_script 在工作階段安裝該任務的套件(pip install --target /tmp/pylib …、/tmp/app 下 npm install …、/tmp/gomod 下 go get …)再執行程式,程式讀取包內資料檔與 $TEAMSYNC_INPUTS_FILE。參考實作:teamsync-backend 的 sandbox/e2e-js/。

腳本檔案與 runtime 契約

腳本檔案有兩條 owner-manager 路由:

  • POST /tasks/{task_id}/script-uploads(multipart file,一個 UTF-8 文字檔 ≤1 MiB)——版本還沒建立就能用。立即驗證腳本並回一個綁在任務上、可重用的 handle(SandboxScriptUploadResponse:id、filename、byte_size、sha256、expires_at——24 小時)。把 id 當 work_script_id 或 startup_script_id 帶進 POST /tasks/{id}/versions 或 PATCH .../versions/{vid};內容會複製進版本,handle 在過期前可重複使用。建版本表單讓使用者選檔案時,走的就是這條。

  • POST /tasks/{task_id}/versions/{vid}/script-file?target=startup|work(multipart,一個 UTF-8 文字檔,≤1 MiB;只限草稿)——直接把檔案內容上傳進 startup_script 或 work_script,語意跟把該欄位 PATCH 成字串完全一樣。檔案有問題回 422 script_too_large/script_not_utf8/script_contains_nul。已發布版本 409(不可變——跟 PATCH 一樣)。

另外還有一條公司裡任何非受限成員都能打的路由,草稿或已發布版本都行(沒有 owner-manager 閘、也沒有限草稿——它用的讀取權限跟 GET /tasks/{id}/versions/{vid} 一樣):

  • POST /tasks/{task_id}/versions/{vid}/input-preview({ "input": <任意 JSON> })——不會派工的 dry run。回傳會真正落地到容器裡的精確 canonical 位元組(input_file_content)、其 digest(input_digest)、runtime_contract(見下)、以及 input_schema 的驗證結果(schema_valid + schema_errors[])。用這個讓作者在發布前先測 input_schema,或讓任何呼叫者(owner 或 borrower)看看自己的 input 實際落地會長什麼樣子。Borrower 的預覽一樣會把 runtime_contract.driver 遮蔽成預設 fallback——只要呼叫者不是這個任務的管理者,handler 在組出 runtime_contract 前就會把 work_command 設為 null。

每個 run 都在一份固定的 runtime 契約下執行:

invocation = ". /workspace/startup.sh && . /workspace/driver.sh" driver = 你的 work_command,空白時是 ". /workspace/work.sh" working_directory = runner 以 cwd=/workspace 啟動 sh,但契約不保證——一律寫絕對路徑 network = 公司設定 runtime_egress_enabled 為 true 時可對外連線(GET /settings;目前預設開啟)——pip / npm / go get 在工作階段可用;對外流量會計量(measured_egress_bytes) inputs_file_env = "TEAMSYNC_INPUTS_FILE" # 存放 input JSON 檔案路徑的環境變數 output_file_env = "TEAMSYNC_OUTPUT_FILE" # 存放結果該寫去哪裡的環境變數 artifacts_dir_env = "TEAMSYNC_ARTIFACTS_DIR" # /workspace/artifacts——留在這裡的檔案會變成可下載的產出物

提交的 input 會在腳本啟動前寫進 $TEAMSYNC_INPUTS_FILE 指的那個檔案(用任何語言讀都行,例如 json.load(open(os.environ["TEAMSYNC_INPUTS_FILE"])));宣告的 secret slot 會以環境變數匯出,不會寫進磁碟。

腳本必須知道的執行期事實(皆為實機觀察):

  • PATH 就是映像設定的 ENV PATH(會驗證:只接受絕對路徑條目);映像沒設時採慣用的 /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin(runner 版本 ≥ #1145/stage image 7abca452…;之前 work shell 根本沒有 PATH,只靠 shell 內建的預設搜尋路徑)。裝在這些目錄之外的工具鏈仍要在 work.sh 裡 export PATH=/usr/local/go/bin:$PATH。runtime_contract.path 會寫明這件事。exit 127(python3: not found)代表直譯器的目錄不在生效的 PATH 上。
  • 工作 shell 的環境變數只有 PATH、你的 ordinary_env、宣告的 secret slot 與三個 TEAMSYNC_* 檔案路徑。映像裡烘焙的其他 ENV(JAVA_HOME、LANG、PYTHON_VERSION……)不會繼承——需要的請在 startup.sh 自行 export。地端部署自 v5.10.11 起行為相同(之前會漏出映像的 ENV)。
  • 託管雲端開啟對外網路的 run(預設)若使用以 v5.10.11 原始碼建置的 runner release,shell 會成為自己的 user+PID 命名空間的 PID 1;主機允許私有 /proc 設定時,$$ 為 1,ps 只看得到這個任務的行程。短命的孤兒行程(例如 BusyBox wget https://… 分出的 ssl_client)在巢狀設定啟用時不再讓 run 以 exit 137 被砍;shell 結束時仍在執行的行程會一併被終止。主機允許 id 對應時 id -u 為 0,否則是溢位 uid 65534 且沒有任何 capability;兩種情況 /workspace 都仍可寫。若主機拒絕命名空間,runner 使用直接 shell fallback。地端工作使用 Docker executor 的直接 shell 路徑,不進入這個 Go gate。Runner 會在 /workspace/.teamsync/ 下寫自己的紀錄——不要使用這個名稱。版本會沿用建置時的 runner(runner_release)。
  • 只有 /workspace 與 /tmp 可寫。快取與安裝放 /tmp(HOME=/tmp、GOPATH=/tmp/go、npm_config_cache=/tmp/npm-cache)。
  • python -m venv 需要能透過 PATH 找到 python3(找不到時會出現 “Unable to determine path to the running Python interpreter”)。沿用映像 PATH 之後,官方 python:* 映像上可正常使用;python3 -m pip install --target /tmp/pylib … 搭配 PYTHONPATH=/tmp/pylib 仍是較輕量的替代作法。
  • 執行之間不保留任何東西:每次執行都重新安裝。

借用者看不到真正的 work_command/driver(會遮蔽成預設的 fallback)——但看得到真正的 input_schema,因為那描述的是呼叫者自己 input 的形狀,不是 owner 的實作細節。

版本升級(binding)

  • 啟用會釘住精確版本。新發布會把 binding/granted-job/選單項目的 update_available 設為 true。
  • 升級:POST /bindings/{binding_id}/accept-version(task_version_id)— 從不自動升。
  • 撤銷釘選:granted-jobs disable,或 POST /bindings/{id}/revoke。兩者都有副作用:該 binding 底下所有 runner 尚未 claim 的 run(狀態 queued)會被立刻標成 cancelled(error_code="binding_revoked"),待確認的 Agent proposal 也會被取消;已 claim 的 run(starting/running…)保留。撤銷 share(.../shares/{grant_id}/revoke)同樣會把該任務在 grant 涵蓋的每個房間裡未 claim 的 run 取消(error_code="share_revoked")。對已終態的 binding 再 revoke → 409 binding_terminal。

擁有者 vs 借用者

  • 你管理的任務/版本:回應含 scripts、work_command、ordinary_env、secret_slot_declarations、output_policy,以及會回顯真正指令的 runtime_contract.driver。
  • 分享給你的任務:那些欄位會省略(work_command 是 null、runtime_contract.driver 只顯示預設 fallback)。借用者永遠看不到 output_policy(裡面嵌了擁有者的 table id,或 Command 與聊天室的 id)。借用者看得到真正的 input_schema——那描述的是自己 input 的形狀,不是 owner 的實作。

常見錯誤

  • 422:owner_scope=chatroom(enum)、欄位驗證、未知欄位。
  • 422 job_contract_invalid:在草稿建立/更新時(發布不會出現這個)——input_example 不是合法 JSON、不符合 input_schema,或 work_command 含 NUL/超過 4096 UTF-8 位元組。
  • 422 input_schema_violation:提交時,run 的 input 不符合版本的 input_schema(派工前就拒絕;只有存好的 schema 本身解析不出來時才會 fail-open——放行並記警告)。
  • 422 script_too_large/script_not_utf8/script_contains_nul:script-file 上傳的檔案有問題。
  • 403 owner_scope_forbidden:呼叫者不能擁有的 department/company scope。
  • 403 output_policy_author_denied:表格 writeback——發布者對目標表沒有不受限寫入權。Command 輸出——撰寫者、發布者或提交者沒有通過身分、grant 或範圍檢查,該 Command 不再是帶 effect_identity 的 restricted definition-authority Command(發布時是下面的 409),或提交 run 的方式不符合任務輸出交給自訂表格 Command的規則。
  • 409 output_policy_owner_scope_unsupported:公司擁有的任務帶 writeback 或 Command 輸出 policy。
  • 409 output_policy_unsatisfiable:表格 writeback,在發布時,以及提交 run 且真的鑄出憑證時——表已不在、有 channel rule,或是 commands_only。Command 輸出:在發布時,三項 Command 檢查(Command 存在、輸入與模式相符、restricted definition authority 且有 effect_identity)任何一項失敗;只有對發布者與已記錄撰寫者的身分、grant 與範圍檢查仍是 403。
  • 422 output_policy_governed_table:草稿建立/更新——表格 writeback policy 指向 write_policy 為 commands_only 的表(它的紀錄只能透過 Command 變更)。
  • 422 output_policy_command_not_found/output_policy_schema_mismatch:Command 輸出,草稿建立/更新時,以及提交 run 時再次檢查——policy 指的不是你公司裡一個活著的 Command,或 input_schema 與該 Command 的 inputs 不同(或該 Command 不是寫入型)。
  • 422 secret_slot_policies_invalid:分享建立時的 secret_slot_policies map 格式不對。
  • 409:already_published/version_not_draft(重複發布)、already_archived/version_not_published(封存)、environment_version_not_ready(發布時環境版本非 ready)、published_version_immutable(PATCH 或 script-file 打在非草稿版本——已發布或已封存都算)、task_not_active(在非 active 的任務上開草稿)、content_scrubbed_irreversible(對已 scrub 的內容發布/PATCH)、share_already_live(同一 task+target 已有 live grant)、binding_already_live(同一 room+task 已有 live binding——POST /bindings 會撞到;enable 對仍適用的 live binding 直接回傳同一個 binding,但 binding 已不適用時也可能出現)、binding_terminal(對已終態的 binding 再 accept-version/revoke)。
  • 422 bundle_path_forbidden:任務包根目錄含 runner 保留檔名(startup.sh、work.sh、driver.sh、input.json)。
  • enable 回 404 “Task not found”:任務還沒分享給該房間(或其部門/公司)——先 POST /tasks/{task_id}/shares。
  • 404:未授權/跨公司分享對象/受限金鑰。

下一步:執行與取回結果。

Last updated on