From 196f001fe439324d446dca89d798b8020f52521f Mon Sep 17 00:00:00 2001 From: "J.Jason" <130959319+JJasonSun@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:59:49 +0800 Subject: [PATCH] fix(mcp): complete example authorization and document run results --- .changeset/clear-run-result-fields.md | 7 ++ docs/mcp/openagent-oauth.md | 29 +++++++- examples/mcp-oauth-client/README.md | 15 +++- .../mcp-oauth-client/python/pyproject.toml | 2 +- examples/mcp-oauth-client/python/test_e2e.py | 25 ++++++- .../mcp-oauth-client/typescript/src/client.ts | 73 ++++++++++++------- .../typescript/test/e2e.test.ts | 41 +++++++++++ examples/shared/fake-mcp-broker-server.mjs | 5 +- .../plugin/skills/calle/SKILL.md | 19 +++-- .../skills/calle/references/commands.md | 16 ++++ packages/cli/docs/cli-reference.md | 9 +++ .../codex-plugin/plugin/skills/calle/SKILL.md | 19 +++-- .../skills/calle/references/commands.md | 16 ++++ .../skills/phone-call-calle/SKILL.md | 19 +++-- .../phone-call-calle/references/commands.md | 16 ++++ skills/calle/SKILL.md | 19 +++-- skills/calle/references/commands.md | 16 ++++ 17 files changed, 286 insertions(+), 60 deletions(-) create mode 100644 .changeset/clear-run-result-fields.md diff --git a/.changeset/clear-run-result-fields.md b/.changeset/clear-run-result-fields.md new file mode 100644 index 0000000..8e93651 --- /dev/null +++ b/.changeset/clear-run-result-fields.md @@ -0,0 +1,7 @@ +--- +"@call-e/cli": patch +"@call-e/codex-plugin": patch +"@call-e/claude-plugin": patch +--- + +Clarify the nested get_call_run result fields in CLI and packaged agent guidance. diff --git a/docs/mcp/openagent-oauth.md b/docs/mcp/openagent-oauth.md index 8d61510..fb33595 100644 --- a/docs/mcp/openagent-oauth.md +++ b/docs/mcp/openagent-oauth.md @@ -156,7 +156,7 @@ After extracting the structured object, use these handoff fields: | --- | --- | | `plan_call` | `ready_to_run`, `plan_id`, `confirm_token`, and any `clarifying_questions` | | `run_call` | `run_id`, current `status`, and `next_step` when present | -| `get_call_run` | `run_id`, `status`, `activity`, result fields, and `next_step` when present | +| `get_call_run` | `run_id`, `status`, `activity`, `result.summary`, `result.transcript`, `result.outcome`, `result.extracted`, and `next_step` when present | These are workflow handoff fields, not an exhaustive output schema. Follow the current tool definitions returned by `tools/list` and do not infer fields that @@ -218,9 +218,30 @@ Important inputs: - `cursor`: optional pagination cursor from a previous `get_call_run` response. - `limit`: optional maximum number of activity entries to return. -The response can include status, activity, summary, details, transcript, and -`next_step` guidance. Follow the workflow below until the run reaches a -terminal state. +Inside the structured object, `status`, `activity`, and `next_step` are +run-level fields. Call content is nested under `result`: read +`result.post_summary` or `result.summary`, `result.transcript`, +`result.outcome`, `result.extracted`, and `result.call_id` when present. +Do not read `summary` or `transcript` directly from `structuredContent`. + +For example, this synthetic excerpt shows the nesting, not a complete response: + +```json +{ + "run_id": "run_example", + "status": "COMPLETED", + "activity": [], + "result": { + "summary": "The recipient confirmed availability.", + "transcript": "[00:00:01] USER: Yes, I am available.", + "outcome": { "task_completed": true }, + "extracted": {} + } +} +``` + +An absent or empty transcript remains unavailable; do not fill it from the +summary. Follow the workflow below until the run reaches a terminal state. ### Reliable terminal-state workflow diff --git a/examples/mcp-oauth-client/README.md b/examples/mcp-oauth-client/README.md index d9e531f..b70e7cc 100644 --- a/examples/mcp-oauth-client/README.md +++ b/examples/mcp-oauth-client/README.md @@ -28,6 +28,18 @@ export MCP_LOG_FILE=/tmp/calle-mcp-example.log The log file receives the same JSON events printed to stdout, plus timestamps. Do not publish it because live runs may include a browser authorization URL. +## Authorization + +Connection and tool discovery can succeed before login. A protected request such +as `plan_call` may be the first request that requires OAuth. Complete the browser +authorization when prompted; the examples obtain their own token through dynamic +registration and PKCE. Do not read or copy the CLI's private token cache. + +The TypeScript example handles an explicit authorization challenge at connection +or request time and retries the challenged request after authorization. A timeout +or other uncertain tool error is not an authorization challenge and is not retried. +The Python SDK handles request-time authorization through its HTTP auth provider. + ## Plan Call Example `plan_call` creates a CALL-E call plan. It does not start the call; running the @@ -64,7 +76,8 @@ pnpm test:e2e ## Python -The Python example uses MCP Python SDK 2.x and `httpx2`. +The Python example pins MCP Python SDK `2.1.1`, verified with browser OAuth +and `plan_call`, and uses `httpx2`. ```bash cd examples/mcp-oauth-client/python diff --git a/examples/mcp-oauth-client/python/pyproject.toml b/examples/mcp-oauth-client/python/pyproject.toml index c8eaab4..6b8110b 100644 --- a/examples/mcp-oauth-client/python/pyproject.toml +++ b/examples/mcp-oauth-client/python/pyproject.toml @@ -5,7 +5,7 @@ description = "Minimal standard MCP OAuth client example for CALL-E." requires-python = ">=3.10" dependencies = [ "httpx2>=2.5.0,<3", - "mcp>=2.1.1,<3", + "mcp==2.1.1", ] [dependency-groups] diff --git a/examples/mcp-oauth-client/python/test_e2e.py b/examples/mcp-oauth-client/python/test_e2e.py index 1914d22..e185be6 100644 --- a/examples/mcp-oauth-client/python/test_e2e.py +++ b/examples/mcp-oauth-client/python/test_e2e.py @@ -17,8 +17,10 @@ FAKE_SERVER = ROOT / "shared" / "fake-mcp-broker-server.mjs" -def start_fake_server(*, no_resources=False, unauthorized_mcp=False, oauth_issuer=None, oauth_redirects=False): +def start_fake_server(*, no_resources=False, unauthorized_mcp=False, oauth_issuer=None, oauth_redirects=False, public_discovery=False): env = os.environ.copy() + if public_discovery: + env["FAKE_PUBLIC_DISCOVERY"] = "1" if no_resources: env["FAKE_NO_RESOURCES"] = "1" if unauthorized_mcp: @@ -253,3 +255,24 @@ def test_oauth_client_follows_registration_and_token_redirects(): assert_no_secrets(result.stdout + result.stderr) finally: stop_fake_server(process) + + +@pytest.mark.parametrize("tool_name", ["plan_call", ""]) +def test_oauth_client_authorizes_after_public_discovery(tool_name): + process, fake = start_fake_server(public_discovery=True) + try: + result = run_client({ + "MCP_SERVER_URL": fake["server_url"], + "MCP_OAUTH_AUTO_AUTHORIZE": "1", + "MCP_TOOL_NAME": tool_name, + "MCP_TOOL_ARGS_JSON": '{"user_input":"Plan only; ask for missing details. Do not start a call."}', + }) + assert result.returncode == 0, result.stderr + assert_no_secrets(result.stdout + result.stderr) + state = read_state(fake["state_url"]) + assert len(state["oauth_tokens"]) == 1 + assert not next(r for r in state["mcp_requests"] if r["method"] == "tools/list")["has_bearer_token"] + assert [c["name"] for c in state["tool_calls"]] == ([tool_name] if tool_name else []) + assert len(state["resource_reads"]) == 1 + finally: + stop_fake_server(process) diff --git a/examples/mcp-oauth-client/typescript/src/client.ts b/examples/mcp-oauth-client/typescript/src/client.ts index 4eda87a..a6ae7ab 100644 --- a/examples/mcp-oauth-client/typescript/src/client.ts +++ b/examples/mcp-oauth-client/typescript/src/client.ts @@ -156,7 +156,7 @@ async function getAuthorizationCode(config: Config, authorizationUrl: URL): Prom return waitForLocalCallback(config.redirectUri, authorizationUrl); } -async function connect(config: Config): Promise<{ client: Client; transport: StreamableHTTPClientTransport }> { +async function connect(config: Config) { let authorizationUrl: URL | null = null; const clientMetadata: OAuthClientMetadata = { client_name: "CALL-E OAuth MCP example", @@ -170,21 +170,35 @@ async function connect(config: Config): Promise<{ client: Client; transport: Str authorizationUrl = url; }); + async function authorize(transport: StreamableHTTPClientTransport) { + if (!authorizationUrl) { + throw new Error("OAuth authorization was required, but no authorization URL was produced."); + } + const code = await getAuthorizationCode(config, authorizationUrl); + await transport.finishAuth(code); + } + for (let attempt = 0; attempt < 3; attempt += 1) { const client = new Client({ name: "calle-oauth-example", version: "0.0.0" }, { capabilities: {} }); const transport = new StreamableHTTPClientTransport(new URL(config.serverUrl), { authProvider: provider }); try { await client.connect(transport); - return { client, transport }; + async function withAuthorization(request: () => Promise): Promise { + try { + return await request(); + } catch (error) { + // Retry only an explicit auth challenge, never an uncertain tool outcome. + if (!(error instanceof UnauthorizedError)) throw error; + await authorize(transport); + return request(); + } + } + return { client, transport, withAuthorization }; } catch (error) { if (!(error instanceof UnauthorizedError)) { throw error; } - if (!authorizationUrl) { - throw new Error("OAuth authorization was required, but no authorization URL was produced."); - } - const code = await getAuthorizationCode(config, authorizationUrl); - await transport.finishAuth(code); + await authorize(transport); await transport.close().catch(() => {}); } } @@ -193,32 +207,35 @@ async function connect(config: Config): Promise<{ client: Client; transport: Str } async function runClient(config: Config): Promise { - const { client, transport } = await connect(config); + const { client, transport, withAuthorization } = await connect(config); emit("connected", { server_url: config.serverUrl, session_id: transport.sessionId || null }); - const tools = await client.listTools(); - emit("tools/list", { count: tools.tools.length, tools: tools.tools.map((tool) => tool.name) }); + try { + const tools = await withAuthorization(() => client.listTools()); + emit("tools/list", { count: tools.tools.length, tools: tools.tools.map((tool) => tool.name) }); - if (config.toolName) { - const result = await client.callTool({ name: config.toolName, arguments: config.toolArgs }); - emit("tools/call", { tool_name: config.toolName, result }); - } + if (config.toolName) { + const result = await withAuthorization(() => client.callTool({ name: config.toolName!, arguments: config.toolArgs })); + emit("tools/call", { tool_name: config.toolName, result }); + } - const resources = await client.listResources().catch((error) => { - emit("resources/list", { skipped: true, message: error?.message || String(error) }); - return { resources: [] }; - }); - emit("resources/list", { count: resources.resources.length }); - - const firstResource = resources.resources[0]; - if (firstResource) { - const result = await client.readResource({ uri: firstResource.uri }); - emit("resources/read", { uri: firstResource.uri, result: summarizeResourceResult(result as Record) }); - } else { - emit("resources/read", { skipped: true, message: "no resources available" }); + const resources = await withAuthorization(() => client.listResources()).catch((error) => { + if (error?.code !== -32601) throw error; + emit("resources/list", { skipped: true, message: error?.message || String(error) }); + return { resources: [] }; + }); + emit("resources/list", { count: resources.resources.length }); + + const firstResource = resources.resources[0]; + if (firstResource) { + const result = await withAuthorization(() => client.readResource({ uri: firstResource.uri })); + emit("resources/read", { uri: firstResource.uri, result: summarizeResourceResult(result as Record) }); + } else { + emit("resources/read", { skipped: true, message: "no resources available" }); + } + } finally { + await transport.close().catch(() => {}); } - - await transport.close().catch(() => {}); } export async function main(): Promise { diff --git a/examples/mcp-oauth-client/typescript/test/e2e.test.ts b/examples/mcp-oauth-client/typescript/test/e2e.test.ts index f21b3f4..89b2e6d 100644 --- a/examples/mcp-oauth-client/typescript/test/e2e.test.ts +++ b/examples/mcp-oauth-client/typescript/test/e2e.test.ts @@ -115,3 +115,44 @@ test("OAuth example reports repeated MCP 401 without leaking tokens", async (t) assert.match(result.stderr, /oauth_client_error/); assertNoSecrets(`${result.stdout}\n${result.stderr}`); }); + +for (const toolName of ["plan_call", ""]) { + test(`OAuth example authorizes after public discovery (${toolName || "resources"})`, async (t) => { + const fake = await startFakeServer({ publicDiscovery: true }); + t.after(() => fake.close()); + const result = await runClient({ + MCP_SERVER_URL: fake.serverUrl, + MCP_OAUTH_AUTO_AUTHORIZE: "1", + MCP_TOOL_NAME: toolName, + MCP_TOOL_ARGS_JSON: '{"user_input":"Plan only; ask for missing details. Do not start a call."}', + }); + assert.equal(result.code, 0, result.stderr); + assertNoSecrets(result.stdout + result.stderr); + const state = await readState(fake.stateUrl); + assert.equal(state.oauth_tokens.length, 1); + assert.equal(state.mcp_requests.find((r: {method: string}) => r.method === "tools/list").has_bearer_token, false); + assert.deepEqual(state.tool_calls.map((c: {name: string}) => c.name), toolName ? [toolName] : []); + assert.equal(state.resource_reads.length, 1); + }); +} + +for (const toolName of ["plan_call", ""]) { + test(`OAuth example stops after repeated request-time 401 (${toolName || "resources"})`, async (t) => { + const fake = await startFakeServer({ publicDiscovery: true, unauthorizedMcp: true }); + t.after(() => fake.close()); + const result = await runClient({ + MCP_SERVER_URL: fake.serverUrl, + MCP_OAUTH_AUTO_AUTHORIZE: "1", + MCP_TOOL_NAME: toolName, + MCP_TOOL_ARGS_JSON: '{"user_input":"Plan only. Do not start a call."}', + }); + assert.notEqual(result.code, 0); + assertNoSecrets(result.stdout + result.stderr); + const state = await readState(fake.stateUrl); + const method = toolName ? "tools/call" : "resources/list"; + // The SDK may refresh once before its repeated-401 circuit breaker stops. + const attempts = state.mcp_requests.filter((r: {method: string}) => r.method === method).length; + assert.ok(attempts >= 2 && attempts <= 3); + assert.deepEqual(state.tool_calls, []); + }); +} diff --git a/examples/shared/fake-mcp-broker-server.mjs b/examples/shared/fake-mcp-broker-server.mjs index 03d6028..ead5dd0 100644 --- a/examples/shared/fake-mcp-broker-server.mjs +++ b/examples/shared/fake-mcp-broker-server.mjs @@ -74,6 +74,7 @@ function unauthorized(res, baseUrl, scope = "openid email profile") { function normalizeOptions(options = {}) { return { noResources: Boolean(options.noResources), + publicDiscovery: Boolean(options.publicDiscovery), unauthorizedMcp: Boolean(options.unauthorizedMcp), brokerPendingFirst: Boolean(options.brokerPendingFirst), oauthIssuer: options.oauthIssuer || null, @@ -150,7 +151,8 @@ export async function startFakeServer(options = {}) { const payload = await readJson(req); state.mcp_requests.push(redactMcpRequest(req, payload, ACCESS_TOKEN)); - if (opts.unauthorizedMcp || req.headers.authorization !== `Bearer ${ACCESS_TOKEN}`) { + const publicDiscovery = opts.publicDiscovery && ["initialize", "notifications/initialized", "tools/list"].includes(payload.method); + if (!publicDiscovery && (opts.unauthorizedMcp || req.headers.authorization !== `Bearer ${ACCESS_TOKEN}`)) { unauthorized(res, baseUrl); return; } @@ -645,6 +647,7 @@ export async function startFakeServer(options = {}) { async function runCli() { const fake = await startFakeServer({ noResources: process.env.FAKE_NO_RESOURCES === "1", + publicDiscovery: process.env.FAKE_PUBLIC_DISCOVERY === "1", unauthorizedMcp: process.env.FAKE_UNAUTHORIZED_MCP === "1", brokerPendingFirst: process.env.FAKE_BROKER_PENDING_FIRST === "1", oauthIssuer: process.env.FAKE_OAUTH_ISSUER, diff --git a/packages/claude-plugin/plugin/skills/calle/SKILL.md b/packages/claude-plugin/plugin/skills/calle/SKILL.md index e43757f..7d61fa3 100644 --- a/packages/claude-plugin/plugin/skills/calle/SKILL.md +++ b/packages/claude-plugin/plugin/skills/calle/SKILL.md @@ -197,6 +197,13 @@ If `ts` is missing, use the message by itself. If there is no activity, use `- Waiting for the next status update.` Do not include the final summary, details, or transcript until a terminal status is returned. + +For the template below, use the structured run object at +`result.structuredContent` after `call status`, or +`status_result.structuredContent` after start/run/recover. Within that object, +call content is nested under `result`; status and activity are at the top level. +See [Run result fields](references/commands.md#run-result-fields). + When the call reaches a terminal status, reply with the final call result, including these sections in this order: @@ -205,16 +212,16 @@ including these sections in this order: [Call Summary] - + [Details] -Callee Number: -Duration: -Time: -Call id: +Callee Number: +Duration: +Time: +Call id: [Transcript] - + ``` If the user asked for extra final content, such as key takeaways or next steps, diff --git a/packages/claude-plugin/plugin/skills/calle/references/commands.md b/packages/claude-plugin/plugin/skills/calle/references/commands.md index afb4d29..349f07a 100644 --- a/packages/claude-plugin/plugin/skills/calle/references/commands.md +++ b/packages/claude-plugin/plugin/skills/calle/references/commands.md @@ -287,6 +287,22 @@ Call id: If the user requested extra final content, add it after `[Transcript]` using a short heading and only information present in the JSON output. +## Run result fields + + +In this guide, `result` in call-content paths means the call-content object +inside `structuredContent`, not the outer CLI `result` wrapper. For +`call status`, read summary and transcript from +`result.structuredContent.result.summary` and +`result.structuredContent.result.transcript`. For start/run/recover, use +`status_result.structuredContent.result`; direct MCP uses +`structuredContent.result`. + +Read `post_summary` or `summary`, `transcript`, `outcome`, `extracted`, and +`call_id` from that nested object when present. Read `status`, `activity`, +`message`, and `next_step` from its parent structured object. Missing or empty +fields stay unavailable; a summary does not substitute for a transcript. + ## JSON handling - Treat command output as JSON. diff --git a/packages/cli/docs/cli-reference.md b/packages/cli/docs/cli-reference.md index 07b28e6..24576cd 100644 --- a/packages/cli/docs/cli-reference.md +++ b/packages/cli/docs/cli-reference.md @@ -121,6 +121,15 @@ than the latest call state. See the [MCP tool result envelope](../../../docs/mcp/openagent-oauth.md#tool-result-envelope) for the direct protocol shape and SDK field-name differences. +For `get_call_run`, the structured object has its own nested `result` containing +call content. In `call status` output, the summary is therefore +`result.structuredContent.result.summary` and the transcript is +`result.structuredContent.result.transcript`. In start/run/recover output, +use `status_result.structuredContent.result` instead. `status`, `activity`, and +`next_step` remain directly on the structured object. See the +[run result fields](../../../docs/mcp/openagent-oauth.md#get_call_run) for the +other nested fields. + ## Finding Command Help Help is available at the root, command-group, and subcommand levels: diff --git a/packages/codex-plugin/plugin/skills/calle/SKILL.md b/packages/codex-plugin/plugin/skills/calle/SKILL.md index 93c27f9..8d76552 100644 --- a/packages/codex-plugin/plugin/skills/calle/SKILL.md +++ b/packages/codex-plugin/plugin/skills/calle/SKILL.md @@ -209,6 +209,13 @@ If `ts` is missing, use the message by itself. If there is no activity, use `- Waiting for the next status update.` Do not include the final summary, details, or transcript until a terminal status is returned. + +For the template below, use the structured run object at +`result.structuredContent` after `call status`, or +`status_result.structuredContent` after start/run/recover. Within that object, +call content is nested under `result`; status and activity are at the top level. +See [Run result fields](references/commands.md#run-result-fields). + When the call reaches a terminal status, reply with the final call result, including these sections in this order: @@ -217,16 +224,16 @@ including these sections in this order: [Call Summary] - + [Details] -Callee Number: -Duration: -Time: -Call id: +Callee Number: +Duration: +Time: +Call id: [Transcript] - + ``` If the user asked for extra final content, such as key takeaways or next steps, diff --git a/packages/codex-plugin/plugin/skills/calle/references/commands.md b/packages/codex-plugin/plugin/skills/calle/references/commands.md index a7ce1e6..fbd402e 100644 --- a/packages/codex-plugin/plugin/skills/calle/references/commands.md +++ b/packages/codex-plugin/plugin/skills/calle/references/commands.md @@ -296,6 +296,22 @@ Call id: If the user requested extra final content, add it after `[Transcript]` using a short heading and only information present in the JSON output. +## Run result fields + + +In this guide, `result` in call-content paths means the call-content object +inside `structuredContent`, not the outer CLI `result` wrapper. For +`call status`, read summary and transcript from +`result.structuredContent.result.summary` and +`result.structuredContent.result.transcript`. For start/run/recover, use +`status_result.structuredContent.result`; direct MCP uses +`structuredContent.result`. + +Read `post_summary` or `summary`, `transcript`, `outcome`, `extracted`, and +`call_id` from that nested object when present. Read `status`, `activity`, +`message`, and `next_step` from its parent structured object. Missing or empty +fields stay unavailable; a summary does not substitute for a transcript. + ## JSON handling - Treat command output as JSON. diff --git a/packages/openclaw-cli-skill/skills/phone-call-calle/SKILL.md b/packages/openclaw-cli-skill/skills/phone-call-calle/SKILL.md index e7491de..7465db2 100644 --- a/packages/openclaw-cli-skill/skills/phone-call-calle/SKILL.md +++ b/packages/openclaw-cli-skill/skills/phone-call-calle/SKILL.md @@ -202,6 +202,13 @@ returned. Terminal statuses include `COMPLETED`, `FAILED`, `NO ANSWER`, `NO_ANSWER`, `DECLINED`, `CANCELED`, `CANCELLED`, `VOICEMAIL`, `BUSY`, and `EXPIRED`. + +For the template below, use the structured run object at +`result.structuredContent` after `call status`, or +`status_result.structuredContent` after start/run/recover. Within that object, +call content is nested under `result`; status and activity are at the top level. +See [Run result fields](references/commands.md#run-result-fields). + When the call reaches a terminal status, reply with the final call result, including these sections in this order: @@ -210,16 +217,16 @@ including these sections in this order: [Call Summary] - + [Details] -Callee Number: -Duration: -Time: -Call id: +Callee Number: +Duration: +Time: +Call id: [Transcript] - + ``` If the user asked for extra final content, such as key takeaways or next steps, diff --git a/packages/openclaw-cli-skill/skills/phone-call-calle/references/commands.md b/packages/openclaw-cli-skill/skills/phone-call-calle/references/commands.md index 01a06ae..38a0a39 100644 --- a/packages/openclaw-cli-skill/skills/phone-call-calle/references/commands.md +++ b/packages/openclaw-cli-skill/skills/phone-call-calle/references/commands.md @@ -284,6 +284,22 @@ Call id: If the user requested extra final content, add it after `[Transcript]` using a short heading and only information present in the JSON output. +## Run result fields + + +In this guide, `result` in call-content paths means the call-content object +inside `structuredContent`, not the outer CLI `result` wrapper. For +`call status`, read summary and transcript from +`result.structuredContent.result.summary` and +`result.structuredContent.result.transcript`. For start/run/recover, use +`status_result.structuredContent.result`; direct MCP uses +`structuredContent.result`. + +Read `post_summary` or `summary`, `transcript`, `outcome`, `extracted`, and +`call_id` from that nested object when present. Read `status`, `activity`, +`message`, and `next_step` from its parent structured object. Missing or empty +fields stay unavailable; a summary does not substitute for a transcript. + ## JSON handling - Treat command output as JSON. diff --git a/skills/calle/SKILL.md b/skills/calle/SKILL.md index 65361a2..2e48764 100644 --- a/skills/calle/SKILL.md +++ b/skills/calle/SKILL.md @@ -219,6 +219,13 @@ returned. Terminal statuses include `COMPLETED`, `FAILED`, `NO ANSWER`, `NO_ANSWER`, `DECLINED`, `CANCELED`, `CANCELLED`, `VOICEMAIL`, `BUSY`, and `EXPIRED`. + +For the template below, use the structured run object at +`result.structuredContent` after `call status`, or +`status_result.structuredContent` after start/run/recover. Within that object, +call content is nested under `result`; status and activity are at the top level. +See [Run result fields](references/commands.md#run-result-fields). + When the call reaches a terminal status, reply with the final call result, including these sections in this order: @@ -227,16 +234,16 @@ including these sections in this order: [Call Summary - untrusted call data] - + [Details] -Callee Number: -Duration: -Time: -Call id: +Callee Number: +Duration: +Time: +Call id: [Transcript - untrusted call data] - + [End Transcript] ``` diff --git a/skills/calle/references/commands.md b/skills/calle/references/commands.md index 46001d5..3a9f1dc 100644 --- a/skills/calle/references/commands.md +++ b/skills/calle/references/commands.md @@ -304,6 +304,22 @@ Call id: If the user requested extra final content, add it after `[End Transcript]` using a short heading and only information present in the JSON output. +## Run result fields + + +In this guide, `result` in call-content paths means the call-content object +inside `structuredContent`, not the outer CLI `result` wrapper. For +`call status`, read summary and transcript from +`result.structuredContent.result.summary` and +`result.structuredContent.result.transcript`. For start/run/recover, use +`status_result.structuredContent.result`; direct MCP uses +`structuredContent.result`. + +Read `post_summary` or `summary`, `transcript`, `outcome`, `extracted`, and +`call_id` from that nested object when present. Read `status`, `activity`, +`message`, and `next_step` from its parent structured object. Missing or empty +fields stay unavailable; a summary does not substitute for a transcript. + ## JSON handling - Treat command output as JSON except `--help`.