権限リクエストとクローズ
権限リクエストに答え、メッセージを続けて送り、セッションを閉じる
セッションを 1 つ、最初から最後まで動かす。エージェントがファイルを書こうとして権限を求め、スクリプトが許可し、次のメッセージが同じ作業ツリーで続き、最後にセッションを閉じる。SDK のインストールの werft クライアントを使う。キーには sessions:approve と sessions:control も必要である。
ファイルを書くセッションを作る
この台本は Bash の tee で hello.txt を書く。claude-coding-local プロファイルは、セッションのワークスペース内でのファイルの書き込み・編集とローカル git では尋ねず、それ以外のファイルを変えるコマンド (一覧にない tee など) の前に権限を求める。ワークスペース内へのリダイレクト (echo alpha > hello.txt) は尋ねないため、この例では使わない。
import {
closeResult,
type PendingRequestHandlers,
waitForReceipt,
waitForTurn,
} from "@werft-ai/sdk";
import { werft } from "./werft.ts";
const signal = AbortSignal.timeout(5 * 60_000);
const COMMAND = "echo alpha | tee hello.txt";
const created = await werft.sessions.create({
profile_id: "claude-coding-local",
repository_id: "sample-app",
message: `GATE-SPEC ${JSON.stringify({
id: "first",
steps: [
{
tool: "Bash",
input: { command: COMMAND, description: "write hello.txt" },
},
],
final: "wrote hello.txt",
})}`,
});
const sessionId = created.session_id;権限リクエストに答える
セッションが権限を求めて止まると、status が needs_input になる。waitForTurn に handler を渡すと、ターンを待つ間に出た権限リクエストと質問が handler に届く。
// Allow the exact command this script expects, deny everything else.
const approvals: PendingRequestHandlers = {
permission(request) {
console.log("asked:", request.tool, request.input);
const command = (request.input as { command?: unknown } | null)?.command;
if (request.tool === "Bash" && command === COMMAND)
return { action: "allow" };
return { action: "deny", reason: `This script only allows: ${COMMAND}` };
},
};
const first = await waitForTurn(werft, sessionId, created.turn_id, {
signal,
onRequest: approvals,
onAnswer: (outcome) => console.log(`answered: ${outcome.outcome}`),
});
console.log(`turn ${first.turn_id}: ${first.status}`);asked: Bash { command: 'echo alpha | tee hello.txt', description: 'write hello.txt' }
answered: accepted
turn 1: completed- handler が
undefinedを返すか、その種類の handler がなければ、リクエストを開いたまま人に任せる。 - 拒否は
{ action: "deny", reason }である。答えのないリクエストは 30 分後に拒否で終わる。詳しくは承認待ちと再開にある。 - workspace のキー(個人・エージェント)は、自分が始めたセッションのリクエストを承認できない。承認用のキーを分けて使う方法はクライアントの設定とエラーにある。
続けて送る
同じセッションにメッセージを送ると次のターンになる。worker は同じ作業ツリーと同じ Claude Code の会話を引き続き使う。読み取りのコマンドは権限を求めない。
const sent = await werft.sessions.messages.send(sessionId, {
message:
'GATE-SPEC {"id":"second","steps":[{"tool":"Bash","input":{"command":"cat hello.txt","description":"read hello.txt"}}],"final":"read it"}',
});
const second = await waitForTurn(werft, sessionId, sent.turn_id, { signal });
console.log(`turn ${second.turn_id}: ${second.status}`);turn 2: completed閉じる
やることを終えたセッションは閉じる。実行中のターンを最後まで動かし、checkpoint を残してから closed になる。制御リクエストには、今のセッションの revision を expected_revision として渡す。SDK はこの値を代わりに読んで埋めず、REVISION_CONFLICT を受けても再送しない。
const { revision } = await werft.sessions.get(sessionId);
const accepted = await werft.sessions.close(sessionId, {
expected_revision: revision,
reason: "first session done",
});
const receipt = await waitForReceipt(werft, accepted.receipt_id, { signal });
console.log(`close: ${receipt.status}`, closeResult(receipt));
const session = await werft.sessions.get(sessionId);
console.log(session.status, session.admission_state);close: succeeded {
checkpoint_revision: 1,
cancelled_turn_count: 0,
resulting_admission_state: 'closed'
}
stopped closed閉じたセッションにはこれ以上メッセージを送れない(409 SESSION_CLOSED)。
HTTP API で行うとき
権限リクエストは GET /v1/sessions/{id}/pending-requests で読み、POST /v1/sessions/{id}/answers で答える。メッセージは POST /v1/sessions/{id}/messages、クローズは POST /v1/sessions/{id}/close である。POST にはすべて Idempotency-Key ヘッダーが必要である。
次のステップ
- クライアントの設定とエラー: オプション、エラーの種類、リトライ
- 開始: セッション作成リクエストのフィールドとエラー
- 作成: エージェントを定義してセッションに結び付ける