cxtms 1.9.267 → 1.9.268

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cxtms",
3
- "version": "1.9.267",
3
+ "version": "1.9.268",
4
4
  "description": "Schema validation package for CXTMS YAML modules",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -65,7 +65,7 @@
65
65
  "type": "object",
66
66
  "required": ["builtin"],
67
67
  "properties": {
68
- "builtin": { "type": "string", "enum": ["data.query", "data.schema", "data.type"], "description": "A built-in, read-only data tool. data.query runs a GraphQL query in the agent's organization (tool data_query); data.schema lists query fields and types (data_schema); data.type describes one type (data_type)." },
68
+ "builtin": { "type": "string", "enum": ["data.query", "data.schema", "data.type", "file.create"], "description": "A built-in tool. data.query runs a GraphQL query in the agent's organization (tool data_query), read-only; data.schema lists query fields and types (data_schema), read-only; data.type describes one type (data_type), read-only; file.create creates a PDF, Word, Excel or CSV file the user can download (tool file_create)." },
69
69
  "instructions": { "type": "string", "description": "When and how the agent should use this tool." },
70
70
  "mode": { "type": "string", "enum": ["auto", "approval", "always"], "default": "auto", "description": "Same meaning as for workflow tools." }
71
71
  },
@@ -438,11 +438,13 @@ the slot name are resolved from component variables and store values before look
438
438
  | `toolbar` | `component[]` | Action components next to tab list |
439
439
  | `header` | `{icon?, title?, subTitle?, buttons?}` | Component arrays rendered above the tab strip |
440
440
  | `useNavigationForTabs` | `boolean` | Push to history instead of replace |
441
+ | `defaultTab` | `string` | Initial tab, matched against the child tab `name`; falls back to the first visible tab |
441
442
 
442
443
  **Tab children props:**
443
444
  | Prop | Type | Description |
444
445
  |------|------|-------------|
445
446
  | `label` | `ILocalizeString` | Tab label (localized, template-parsed) |
447
+ | `icon` | `string` | Icon name rendered beside the label on web and mobile, including overflow menus |
446
448
  | `isVisible` | `string` | Template expression — show when truthy |
447
449
  | `isHidden` | `string` | Template expression — hide when truthy |
448
450
  | `options` | `object` | Additional Tab element props |
@@ -453,6 +455,11 @@ the slot name are resolved from component variables and store values before look
453
455
  component: tabs
454
456
  name: detailTabs
455
457
  props:
458
+ defaultTab: general
459
+ header:
460
+ title:
461
+ - component: text
462
+ props: { value: { en-US: "Details" } }
456
463
  toolbar:
457
464
  - component: button
458
465
  name: refreshBtn
@@ -461,6 +468,7 @@ children:
461
468
  - name: general
462
469
  props:
463
470
  label: { en-US: "General" }
471
+ icon: info
464
472
  children:
465
473
  - component: field
466
474
  name: name
@@ -7,10 +7,11 @@
7
7
  - Inputs: how they become the first message, and the tool schema seen by callers
8
8
  - The built-in `set_result` tool and `agent.result`
9
9
  - The `__session` override input
10
- - Outputs: `result`, `transcript`, `sessionId`
10
+ - Outputs: `result`, `transcript`, `sessionId`, `files`
11
11
  - Sessions: how an agent is invoked — a workflow task vs. the Responses API chat
12
12
  - Tool approval (`tools[].mode`) and the chat's Ask/Auto approval mode
13
- - Built-in data tools (`tools[].builtin`): `data.query`, `data.schema`, `data.type`
13
+ - Built-in tools (`tools[].builtin`): `data.query`, `data.schema`, `data.type`, `file.create`
14
+ - Producing files: `file.create`, captured workflow-tool files, where they appear
14
15
  - History compression
15
16
  - Files in chat: what the model receives, and the model config's `supportsFiles` flag
16
17
  - Session ownership and live events
@@ -48,7 +49,7 @@ agent: # Required (replaces activities)
48
49
  agents: [...]
49
50
 
50
51
  inputs: [...] # Becomes the agent's first message
51
- # outputs: not needed — result/transcript/sessionId are fixed and always produced
52
+ # outputs: not needed — result/transcript/sessionId/files are fixed and always produced
52
53
  ```
53
54
 
54
55
  ## Agent Section — Property Reference
@@ -76,7 +77,7 @@ inputs: [...] # Becomes the agent's first message
76
77
  | `skills` | array of strings | — | Installed skill names to enable. Runtime support ships in a later release; safe to declare now. |
77
78
  | `tools` | array of objects | — | Tools the agent may call: other workflows (`workflow`) or built-in data tools (`builtin`). Each entry has exactly one of the two. |
78
79
  | `tools[].workflow` | string (`minLength: 1`) | — | Workflow name or `workflowId` to expose as a tool. Exactly one of `workflow`/`builtin` per entry. |
79
- | `tools[].builtin` | string, enum `data.query` \| `data.schema` \| `data.type` | — | A built-in, read-only data tool — see [Built-in data tools](#built-in-data-tools-toolsbuiltin). Exactly one of `workflow`/`builtin` per entry. |
80
+ | `tools[].builtin` | string, enum `data.query` \| `data.schema` \| `data.type` \| `file.create` | — | A built-in tool: the three read-only data tools, or `file.create`, which creates a PDF, Word, Excel or CSV file the user can download — see [Built-in tools](#built-in-tools-toolsbuiltin). Exactly one of `workflow`/`builtin` per entry. |
80
81
  | `tools[].instructions` | string | — | When and how the agent should use this tool — folded into the tool's description for the model. |
81
82
  | `tools[].mode` | string, enum `auto` \| `approval` \| `always` | `auto` | `auto` runs the tool as soon as the model calls it. `approval` pauses the call for a person unless the chat is in Auto mode. `always` pauses for a person in every chat — see [Tool approval](#tool-approval-toolsmode). |
82
83
  | `mcp` | array of objects | — | Outbound MCP server connections. Runtime support ships in a later release. |
@@ -126,17 +127,18 @@ Pass `__session` as an input (it does not need to be declared in `inputs:`) to o
126
127
 
127
128
  ## Outputs
128
129
 
129
- Outputs are **fixed** for Agent workflows — when the session completes via `set_result`, the engine always produces exactly these three, regardless of what (if anything) you declare under `outputs:`:
130
+ Outputs are **fixed** for Agent workflows — when the session completes via `set_result`, the engine always produces exactly these four, regardless of what (if anything) you declare under `outputs:`:
130
131
 
131
132
  | Output | Description |
132
133
  |--------|-------------|
133
134
  | `result` | The argument the agent passed to `set_result`, matching `agent.result`'s schema. |
134
135
  | `transcript` | The full turn-by-turn conversation log for the session (prompts, tool calls, tool results, model responses). |
135
136
  | `sessionId` | The session identifier. |
137
+ | `files` | Every file the session produced (`file.create`, or a captured workflow-tool file — see [Producing files](#producing-files)), each `{ attachmentId, fileName, contentType, size, url }`. `url` is presigned for 24 hours, or `null` if signing it failed (logged; the task still completes). `[]`, not omitted, when nothing was produced. |
136
138
 
137
- If a `task` session ends **without** calling `set_result` — it hits `session.maxTurns`, `session.timeout`, or stops responding with tool calls after two nudges — the workflow **fails**: the engine raises an error naming the session id (e.g. `Agent session <sessionId> ended without a result: exhausted its turn budget (20)`), and there are **no** `result`/`transcript`/`sessionId` outputs in that case. Look up the `AgentSession` row by the session id in the error message to inspect the transcript of a failed run.
139
+ If a `task` session ends **without** calling `set_result` — it hits `session.maxTurns`, `session.timeout`, or stops responding with tool calls after two nudges — the workflow **fails**: the engine raises an error naming the session id (e.g. `Agent session <sessionId> ended without a result: exhausted its turn budget (20)`), and there are **no** `result`/`transcript`/`sessionId`/`files` outputs in that case. Look up the `AgentSession` row by the session id in the error message to inspect the transcript of a failed run.
138
140
 
139
- An `outputs:` section is **not required** for an Agent workflow — the scaffolded template omits it entirely, and `result`/`transcript`/`sessionId` are still produced when the session completes. The engine ignores `outputs:` for this workflow type: it does not consult it to decide what to produce. If you add an `outputs:` section anyway (e.g. to rename an output for a caller, or because a shared tool expects one), the normal `output.json` rule still applies — each entry still needs a `mapping` — but it has no effect on which outputs the Agent runtime actually populates.
141
+ An `outputs:` section is **not required** for an Agent workflow — the scaffolded template omits it entirely, and `result`/`transcript`/`sessionId`/`files` are still produced when the session completes. The engine ignores `outputs:` for this workflow type: it does not consult it to decide what to produce. If you add an `outputs:` section anyway (e.g. to rename an output for a caller, or because a shared tool expects one), the normal `output.json` rule still applies — each entry still needs a `mapping` — but it has no effect on which outputs the Agent runtime actually populates.
140
142
 
141
143
  ## Sessions: How an Agent Is Invoked
142
144
 
@@ -145,7 +147,7 @@ The same `agent:` YAML backs two different ways of running an agent, and the cal
145
147
  | | **Task session** | **Chat session** |
146
148
  |---|---|---|
147
149
  | Started by | A workflow run: direct invocation, a trigger, `Workflow/Execute@1`, or another agent's `tools[]`/`agents[]` | A conversation held over the OpenAI-Responses-compatible API (`POST /api/organizations/{id}/ai/responses`, or the public-api equivalent) |
148
- | `set_result` tool | Registered; calling it ends the session and produces `result`/`transcript`/`sessionId` (see [Outputs](#outputs)) | Not registered at all |
150
+ | `set_result` tool | Registered; calling it ends the session and produces `result`/`transcript`/`sessionId`/`files` (see [Outputs](#outputs)) | Not registered at all |
149
151
  | Ends when | `set_result` is called, or `session.maxTurns`/`session.timeout` is hit | The client stops sending turns, or `session.maxTurns`/`session.timeout` is hit — there is no `set_result` to end it early |
150
152
  | Approval tools | Refused outright — see [Tool approval](#tool-approval-toolsmode) | Pause the conversation for a person to decide |
151
153
  | Owner default | `Organization` (no person is present to default to) | `User` — the session's starter |
@@ -177,9 +179,11 @@ without an explicit click, whatever the user's mode — voiding an invoice, char
177
179
 
178
180
  **In a task session** (no person is present to ask): an approval tool is **refused** the moment the model calls it — the agent is told it needs human approval for that call and continues reasoning from there (typically escalating via `set_result` rather than completing the original action). Don't rely on `mode: approval` to gate a tool inside a task-only agent; a task session can never satisfy it. If an agent needs to run in both modes, write its `instructions` to handle the "this needs a person" outcome explicitly (see `AGT_010` below for the schema-level check on `mode`'s value; there is no schema check for whether an agent using `mode: approval` will ever run as a chat).
179
181
 
180
- ## Built-in Data Tools (`tools[].builtin`)
182
+ ## Built-in Tools (`tools[].builtin`)
181
183
 
182
- Three built-in tools let an agent read the organization's data through GraphQL without a workflow per question:
184
+ Four built-in tools cover reading the organization's data and handing the user a file, without a workflow per
185
+ use case: three read-only data tools over GraphQL, and `file.create`, which writes a PDF, Word, Excel or CSV
186
+ file to the session.
183
187
 
184
188
  ```yaml
185
189
  agent:
@@ -190,6 +194,8 @@ agent:
190
194
  - builtin: data.type
191
195
  instructions: "Look up field types before writing a query."
192
196
  - builtin: data.query
197
+ - builtin: file.create
198
+ instructions: "Use this to export results the user asks to download."
193
199
  - workflow: "Assistant / Cancel Shipment" # changing data still goes through a workflow tool
194
200
  mode: approval
195
201
  ```
@@ -199,10 +205,163 @@ agent:
199
205
  | `data.query` | `data_query` | Runs one GraphQL **query** (`query`, optional `variables`, `operationName`) in the agent's organization and returns `{ data, errors? }`. |
200
206
  | `data.schema` | `data_schema` | Lists query fields (`category: queries`, the default), types (`types`) or both (`all`), with an optional case-insensitive `filter`. Mutations are not listed. |
201
207
  | `data.type` | `data_type` | Describes one type (`typeName`): fields, types, arguments, descriptions, enum values; an unknown name returns up to 5 suggestions. |
208
+ | `file.create` | `file_create` | Writes a file to the session: `markdown` renders to `pdf`/`docx`; `rows` or `query` renders to `xlsx`/`csv`. Returns `{ fileName, attachmentId, format, size, rows?, truncated? }` — never the file's content. See [`file.create`](#filecreate-toolsbuiltin-filecreate) below. |
209
+
210
+ Guards, enforced by the backend, on `data.query` (and on `file.create`'s `query` mode, which runs through the
211
+ same gateway): query operations only (mutations and subscriptions return `read_only` — change data with a
212
+ workflow tool, ideally `mode: approval`); every root Query field must declare `organizationId` and pass the
213
+ session's organization, except `currentUser`, `personalAccessToken`/`personalAccessTokens`, `hasUserSecret`
214
+ and introspection (`__schema`/`__type`/`__typename`) — anything else is refused as `organization_scope`
215
+ ("Field {name} is not scoped to an organization and cannot be queried."), which fails closed for new
216
+ resolvers; `organizations` is refused too, even though it takes `organizationId` — its resolver ignores the
217
+ argument; `exportRates` (uploads an export file) and `uploadUrl` (issues a presigned upload URL) take
218
+ `organizationId` but have side effects, so they are refused as `read_only`, even in the session's organization
219
+ — use a workflow tool for them; `organizationConfig`, `organizationConfigs`, `contactPaymentMethod`,
220
+ `contactPaymentMethods`, `outboxMessages`, `deadLetterMessages` and `outboxStatus` hold secrets, payment data
221
+ or internal infrastructure data and are refused as `restricted` ("Field {name} holds sensitive data and cannot
222
+ be queried by agents."), whatever alias or fragment is used; inside `where:` filters, `organizationId` accepts
223
+ only `{ eq: <org> }` or `{ in: [<org>] }`; selection depth at most 12 (`depth_exceeded`; `__schema`/`__type`-only
224
+ queries are exempt); duplicate keys in `variables` are rejected as `syntax`; `data.query` results over 64 KB
225
+ are cut and marked `truncated` with a hint to page with `take`/`skip` (`file.create`'s `query` mode pages
226
+ itself instead — see below). The runner also tells the model which organization it is in whenever a data tool
227
+ is enabled.
228
+
229
+ `mode` and `instructions` work as for workflow tools. Built-ins run as the session user, so row-level security
230
+ applies. A workflow tool whose derived name would collide with `data_query`/`data_schema`/`data_type`/
231
+ `file_create` is suffixed (`data_query_2`).
232
+
233
+ ### `file.create` (`tools[].builtin: file.create`)
234
+
235
+ Pass exactly one of `markdown` (renders to `pdf`/`docx`) or `rows`/`query` (renders to `xlsx`/`csv`):
202
236
 
203
- Guards, enforced by the backend: query operations only (mutations and subscriptions return `read_only` — change data with a workflow tool, ideally `mode: approval`); every root Query field must declare `organizationId` and pass the session's organization, except `currentUser`, `personalAccessToken`/`personalAccessTokens`, `hasUserSecret` and introspection (`__schema`/`__type`/`__typename`) — anything else is refused as `organization_scope` ("Field {name} is not scoped to an organization and cannot be queried."), which fails closed for new resolvers; `organizations` is refused too, even though it takes `organizationId` — its resolver ignores the argument; `exportRates` (uploads an export file) and `uploadUrl` (issues a presigned upload URL) take `organizationId` but have side effects, so they are refused as `read_only`, even in the session's organization — use a workflow tool for them; `organizationConfig`, `organizationConfigs`, `contactPaymentMethod`, `contactPaymentMethods`, `outboxMessages`, `deadLetterMessages` and `outboxStatus` hold secrets, payment data or internal infrastructure data and are refused as `restricted` ("Field {name} holds sensitive data and cannot be queried by agents."), whatever alias or fragment is used; inside `where:` filters, `organizationId` accepts only `{ eq: <org> }` or `{ in: [<org>] }`; selection depth at most 12 (`depth_exceeded`; `__schema`/`__type`-only queries are exempt); duplicate keys in `variables` are rejected as `syntax`; results over 64 KB are cut and marked `truncated` with a hint to page with `take`/`skip`. The runner also tells the model which organization it is in whenever a data tool is enabled.
237
+ ```yaml
238
+ agent:
239
+ tools:
240
+ - builtin: file.create
241
+ instructions: "Export query results to Excel when the user asks to download them."
242
+ ```
243
+
244
+ | Argument | Type | Meaning |
245
+ |---|---|---|
246
+ | `format` | string, enum `pdf` \| `docx` \| `xlsx` \| `csv` | Required. |
247
+ | `fileName` | string | Required. The name without an extension — it is sanitized, and the extension comes from `format`. |
248
+ | `markdown` | string | For `pdf`/`docx`: the document body — a Markdown subset (pipe tables, autolinks, strikethrough/emphasis extras). |
249
+ | `title` | string | Optional, `pdf`/`docx`: the header title, at most 200 characters. Defaults to `fileName`. |
250
+ | `rows` | array of objects | For `xlsx`/`csv`: one object per row; its keys are the columns. |
251
+ | `query` | string | For `xlsx`/`csv`, instead of `rows`: a GraphQL query the server runs and pages through — same guards as `data.query` (read-only, organization-scoped, depth-limited). |
252
+ | `variables` | object | Optional, with `query`. May also arrive as a JSON string holding an object; blank or absent means no variables. |
253
+ | `itemsPath` | string | Required with `query`: the path to the list in the result, e.g. `orders.items`. |
254
+ | `columns` | array of `{ field, label? }` | Optional, `rows`/`query`: the column order and headings. `field` may be a dot path into a nested object (`orderStatus.orderStatusName`). Omit it to use every property found on the rows, in encounter order. |
255
+
256
+ **Rules:**
257
+
258
+ - Exactly one of `markdown`, `rows` or `query`, matching the format (`markdown` for `pdf`/`docx`; `rows` or
259
+ `query` for `xlsx`/`csv`) — a mismatch is `invalid_arguments`.
260
+ - In `query` mode the query **must declare `$skip: Int` and `$take: Int`** and pass them to the list field —
261
+ the server pages through the results itself (500 rows a page); a query missing either variable is
262
+ `invalid_arguments`.
263
+ - Raw HTML in `markdown` is disabled and stays literal text (never executed or templated); images are
264
+ replaced by their alt text, so rendering never fetches a remote URL.
265
+ - In `csv`, every string cell and column label starting with `=`, `+`, `-`, `@`, a tab or a carriage return is
266
+ prefixed with `'`, so spreadsheet apps open it as text instead of running it as a formula. `xlsx` stores
267
+ strings as text values, which never run as formulas, so they're written unchanged.
268
+
269
+ **Limits:**
270
+
271
+ | Limit | Value |
272
+ |---|---|
273
+ | `markdown` length | 200,000 characters (`too_large` past that) |
274
+ | `rows` entries | 50,000 (`too_large` past that; use `query` for a bigger export) |
275
+ | `query` row cap | 50,000 rows, or 64 MB of JSON read, whichever comes first — paging stops and the result's `truncated` is `true` rather than the call failing |
276
+ | `title` length | 200 characters |
277
+ | Rendered file size | 25 MB |
278
+
279
+ **Errors**, on top of the `data.query` guards when `query` is used:
280
+
281
+ | Case | Result |
282
+ |---|---|
283
+ | Unknown `format`, missing `fileName`, `title` over 200 characters, none or more than one of `markdown`/`rows`/`query`, `rows` not an array, `query` missing `itemsPath` or `$skip`/`$take`, `itemsPath` not pointing at a list | `{ "error": { "code": "invalid_arguments", "message": "…" } }` |
284
+ | `markdown` over 200,000 characters, `rows` over 50,000 entries, a `query` page over 8 MB even at 50 rows, or the rendered file over 25 MB | `{ "error": { "code": "too_large", "message": "…" } }` |
285
+ | Rendering the PDF/Word/spreadsheet threw | `{ "error": { "code": "render_failed", "message": "Could not create {fileName}." } }` |
286
+ | Saving the file threw, or the `query` itself failed | `{ "error": { "code": "failed", "message": "…" } }` |
287
+
288
+ A successful call returns `{ fileName, attachmentId, format, size, rows?, truncated? }` (`rows`/`truncated`
289
+ only for `xlsx`/`csv`). The file itself is never sent back to the model — tell the user it's attached rather
290
+ than repeating its contents as text. See [Producing files](#producing-files) for where the file ends up.
291
+
292
+ #### Barcodes
293
+
294
+ In `pdf` and `docx` documents, a Markdown image whose URL starts with `barcode:` is drawn as a barcode on the
295
+ server (nothing is fetched). It works in paragraphs, list items and table cells. Barcodes are not supported in
296
+ `xlsx`/`csv`.
297
+
298
+ ```markdown
299
+ ![ORD-1001](barcode:code128/ORD-1001)
300
+ ![P-01](barcode:qr/P-01?width=1in)
301
+ ![PLT-77](barcode:pdf417/PLT-77?width=3in&height=1in)
302
+ | Order | Label |
303
+ |---|---|
304
+ | ORD-1001 | ![ORD-1001](barcode:code128/ORD-1001?width=50mm&text=false) |
305
+ ```
204
306
 
205
- `mode` and `instructions` work as for workflow tools. Built-ins run as the session user, so row-level security applies. A workflow tool whose derived name would collide with `data_query`/`data_schema`/`data_type` is suffixed (`data_query_2`).
307
+ - **Address:** `barcode:<format>/<value>`, with the value URL-encoded. The alt text becomes the image's
308
+ alternative text.
309
+ - **Formats** (case-insensitive; ZXing enum names such as `CODE_128` also work):
310
+ - linear: `code128`, `code39`, `code93`, `codabar`, `itf`, `msi`, `plessey`, `ean13`, `ean8`, `upca`, `upce`;
311
+ - 2D: `qr`, `datamatrix`, `aztec`, `pdf417`.
312
+ - **Options** (all optional):
313
+ - `width` and `height`, as a number plus `in`, `mm` or `cm`, each 5–200 mm;
314
+ - `text=false` hides the value printed under linear codes (2D codes never print it).
315
+
316
+ **Sizes:**
317
+
318
+ | Kind | Default | Only one dimension given | Both given |
319
+ |---|---|---|---|
320
+ | Linear | 2.5 in × 0.6 in | the other keeps the ratio (height = width × 0.24) | used as given |
321
+ | QR, Data Matrix, Aztec | 1.2 in square | stays square | square, using the smaller |
322
+ | PDF417 | 2.5 in × 1 in | the other keeps the ratio (height = width × 0.4) | used as given |
323
+
324
+ - Every module (bar or cell) is at least 0.17 mm, so the code prints and scans.
325
+ - When no `width` is given, a code too dense for the default width grows just wide enough, up to 200 mm.
326
+ - An explicit size that is too small, or content needing more than 200 mm, is an error naming the minimum width.
327
+ - In a PDF, a barcode wider than its table cell shrinks proportionally to fit.
328
+ - QR, Data Matrix, Aztec and PDF417 encode UTF-8.
329
+ - EAN-13, UPC-A and EAN-8 values given without their check digit print it.
330
+
331
+ **Limits:** a value can be at most 1,000 characters, and a document can hold at most 200 barcodes.
332
+
333
+ **Errors:** a bad barcode fails the call with `invalid_arguments` naming the problem, and no file is saved, so the
334
+ model can fix it and retry. Examples:
335
+ - `unknown barcode format 'code11'`
336
+ - `width '500mm' is larger than 200mm`
337
+ - `ean13 cannot encode 'ABC'`
338
+ - `code128 value '…' needs a width of at least 23mm`
339
+
340
+ ## Producing Files
341
+
342
+ A file an agent hands the user — from `file.create` or from a workflow tool — is captured automatically as a
343
+ produced file on the session; you don't wire up storage or the transcript yourself.
344
+
345
+ **Where files come from:**
346
+
347
+ - `builtin: file.create` (above).
348
+ - A workflow tool's own output, captured the same way: a Document workflow's `file`/`fileName` output, or
349
+ `Utilities/Export`'s `fileStream`/`fileUrl`. The runner reads the file, saves it, and replaces it in what the
350
+ model sees with `{ fileName, attachmentId }` — or `{ fileName, skipped: "<reason>" }` if it couldn't be kept
351
+ (over 25 MB, unreadable, or couldn't be saved). The rest of the tool's result is untouched either way. When
352
+ the tool returns a `response` output, the model sees `{ response, files: [<entries>] }`.
353
+
354
+ **Where files appear:**
355
+
356
+ - As cards on the agent's reply in chat.
357
+ - In the AI Assistant's Library, listed as "Produced in *chat*".
358
+ - In the transcript, as `attachment` blocks (`attachmentId`, `fileName`, `contentType`, `size`) on the tool's
359
+ message, carrying `"origin": "produced"` — unlike a user-uploaded file, which has no `origin` key.
360
+
361
+ Produced files are **never re-sent to the model** on later turns — the block is dropped when history is
362
+ rebuilt for a fresh model context, so render a produced file from the transcript, not from anything the model
363
+ says about it. See [Outputs](#outputs) for the `files` a task run returns, and `docs/agent-api.md` §14 "Built-in
364
+ tools" and §10 (transcript `attachment` blocks) in `tms-backend-api` for the full shapes.
206
365
 
207
366
  ## History Compression
208
367
 
@@ -322,5 +481,5 @@ These are backend validation codes; `cxtms` validates the same constraints clien
322
481
  | `AGT_011` | `ui.color` must be one of `primary`, `secondary`, `info`, `success`, `warning`, `error` | Set `agent.ui.color` to one of the six palette names, lowercase, or omit it. |
323
482
  | `AGT_012` | `ui.prompts` has more than 5 entries | Keep at most 5 `agent.ui.prompts`. |
324
483
  | `AGT_013` | a `tools[]` entry needs exactly one of `workflow`/`builtin` | Give each `agent.tools[]` entry either `workflow` (name or `workflowId`) or `builtin`, not both and not neither. |
325
- | `AGT_014` | unknown `tools[].builtin` | Use `data.query`, `data.schema` or `data.type` (case-sensitive). |
484
+ | `AGT_014` | unknown `tools[].builtin` | Use `data.query`, `data.schema`, `data.type` or `file.create` (case-sensitive). |
326
485
  | `AGT_015` | the same `builtin` listed twice | List each built-in tool once. |