@xingular/harness 0.0.0-stage → 0.1.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.
Files changed (135) hide show
  1. package/README.md +269 -2
  2. package/dist/cli.js +11510 -0
  3. package/dist/terminal-pi.js +326 -0
  4. package/package.json +41 -4
  5. package/src/accounts/accounts.test.ts +223 -0
  6. package/src/accounts/accounts.ts +522 -0
  7. package/src/accounts/env.test.ts +95 -0
  8. package/src/accounts/env.ts +338 -0
  9. package/src/accounts/secrets.ts +50 -0
  10. package/src/accounts/store.ts +338 -0
  11. package/src/capabilities/api.ts +75 -0
  12. package/src/capabilities/runSnapshot.ts +59 -0
  13. package/src/capabilities/types.ts +34 -0
  14. package/src/cli.ts +110 -0
  15. package/src/core/bus.ts +34 -0
  16. package/src/core/changes.ts +33 -0
  17. package/src/core/env.ts +27 -0
  18. package/src/core/errors.ts +32 -0
  19. package/src/core/frontmatter.ts +47 -0
  20. package/src/core/fs.test.ts +38 -0
  21. package/src/core/fs.ts +92 -0
  22. package/src/core/index.ts +193 -0
  23. package/src/core/path.ts +45 -0
  24. package/src/core/projects.ts +90 -0
  25. package/src/core/text.ts +39 -0
  26. package/src/harness.ts +299 -0
  27. package/src/index.ts +70 -0
  28. package/src/mcp/connections.ts +614 -0
  29. package/src/mcp/manager.ts +130 -0
  30. package/src/mcp/oauth.ts +195 -0
  31. package/src/mcp/scopeMigration.ts +204 -0
  32. package/src/mcp/service.test.ts +376 -0
  33. package/src/mcp/service.ts +549 -0
  34. package/src/mcp/types.ts +108 -0
  35. package/src/memory/api.test.ts +82 -0
  36. package/src/memory/api.ts +289 -0
  37. package/src/memory/blocks.test.ts +78 -0
  38. package/src/memory/blocks.ts +406 -0
  39. package/src/memory/caller.ts +57 -0
  40. package/src/memory/dispatch.ts +59 -0
  41. package/src/memory/facts.test.ts +41 -0
  42. package/src/memory/facts.ts +248 -0
  43. package/src/memory/graph.ts +103 -0
  44. package/src/memory/health.test.ts +130 -0
  45. package/src/memory/health.ts +74 -0
  46. package/src/memory/maintainer.ts +272 -0
  47. package/src/memory/maintenance.test.ts +176 -0
  48. package/src/memory/maintenance.ts +330 -0
  49. package/src/memory/migrate.ts +79 -0
  50. package/src/memory/operations.ts +138 -0
  51. package/src/memory/proposals.test.ts +168 -0
  52. package/src/memory/proposals.ts +576 -0
  53. package/src/memory/recall.test.ts +104 -0
  54. package/src/memory/recall.ts +271 -0
  55. package/src/memory/references.ts +77 -0
  56. package/src/memory/runContext.ts +93 -0
  57. package/src/memory/secrets.ts +6 -0
  58. package/src/memory/store.test.ts +66 -0
  59. package/src/memory/store.ts +385 -0
  60. package/src/memory/wiki.test.ts +47 -0
  61. package/src/memory/wiki.ts +422 -0
  62. package/src/memory/wikiIndex.test.ts +88 -0
  63. package/src/memory/wikiIndex.ts +116 -0
  64. package/src/plugins/api.ts +181 -0
  65. package/src/plugins/attachments.test.ts +84 -0
  66. package/src/plugins/attachments.ts +141 -0
  67. package/src/plugins/builtin.ts +82 -0
  68. package/src/plugins/catalogue.test.ts +254 -0
  69. package/src/plugins/catalogue.ts +251 -0
  70. package/src/plugins/cli.ts +184 -0
  71. package/src/plugins/discover.ts +151 -0
  72. package/src/plugins/firstParty.test.ts +143 -0
  73. package/src/plugins/firstParty.ts +129 -0
  74. package/src/plugins/hooks.test.ts +250 -0
  75. package/src/plugins/hooks.ts +239 -0
  76. package/src/plugins/identity.ts +37 -0
  77. package/src/plugins/install.test.ts +107 -0
  78. package/src/plugins/install.ts +106 -0
  79. package/src/plugins/inventory.ts +123 -0
  80. package/src/plugins/manifest.ts +76 -0
  81. package/src/plugins/plugins.test.ts +205 -0
  82. package/src/plugins/plugins.ts +189 -0
  83. package/src/plugins/proposal.ts +121 -0
  84. package/src/plugins/runtime.test.ts +40 -0
  85. package/src/plugins/runtime.ts +82 -0
  86. package/src/plugins/trust.ts +142 -0
  87. package/src/plugins/types.ts +149 -0
  88. package/src/prompt/compile.test.ts +261 -0
  89. package/src/prompt/compile.ts +732 -0
  90. package/src/server/methods.ts +314 -0
  91. package/src/server/remote.ts +77 -0
  92. package/src/server/rpc.ts +116 -0
  93. package/src/server/serve.ts +28 -0
  94. package/src/server/server.test.ts +174 -0
  95. package/src/sessions/approvals.ts +118 -0
  96. package/src/sessions/contracts.ts +299 -0
  97. package/src/sessions/conversation.test.ts +150 -0
  98. package/src/sessions/extensionSession.test.ts +74 -0
  99. package/src/sessions/extensionUi.test.ts +65 -0
  100. package/src/sessions/extensionUi.ts +102 -0
  101. package/src/sessions/history.ts +51 -0
  102. package/src/sessions/lineageJoin.ts +75 -0
  103. package/src/sessions/policy.ts +32 -0
  104. package/src/sessions/pricing.ts +60 -0
  105. package/src/sessions/reasoningSummaries.ts +59 -0
  106. package/src/sessions/registry.ts +294 -0
  107. package/src/sessions/runIndex.ts +99 -0
  108. package/src/sessions/runStore.test.ts +152 -0
  109. package/src/sessions/runStore.ts +332 -0
  110. package/src/sessions/runner.ts +556 -0
  111. package/src/sessions/runtimeResources.test.ts +61 -0
  112. package/src/sessions/sessions.test.ts +434 -0
  113. package/src/sessions/sessions.ts +295 -0
  114. package/src/skills/registry.test.ts +104 -0
  115. package/src/skills/registry.ts +407 -0
  116. package/src/terminal/cli.ts +149 -0
  117. package/src/terminal/client.ts +74 -0
  118. package/src/terminal/controller.ts +227 -0
  119. package/src/terminal/piExtension.ts +208 -0
  120. package/src/terminal/pluginControl.test.ts +146 -0
  121. package/src/terminal/pluginMenu.ts +112 -0
  122. package/src/terminal/socket.ts +182 -0
  123. package/src/terminal/standalone.test.ts +115 -0
  124. package/src/terminal/standalone.ts +126 -0
  125. package/src/terminal/terminal.test.ts +302 -0
  126. package/src/terminal/terminals.ts +429 -0
  127. package/src/tools/askUser.ts +45 -0
  128. package/src/tools/definitions.ts +187 -0
  129. package/src/tools/memoryTools.ts +22 -0
  130. package/src/tools/pluginPropose.ts +41 -0
  131. package/src/tools/types.ts +36 -0
  132. package/src/web/safeRequest.ts +171 -0
  133. package/src/web/text.ts +53 -0
  134. package/src/web/tools.ts +134 -0
  135. package/src/web/web.test.ts +141 -0
package/README.md CHANGED
@@ -1,3 +1,270 @@
1
- # Temporary Holding Version
1
+ # Xingular Harness
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A Pi agent harness, as one TypeScript package (`@xingular/harness`). It is everything a model
4
+ needs to work for someone over time, and nothing about any particular application:
5
+
6
+ | Part | Folder | What it does |
7
+ |---|---|---|
8
+ | Accounts | `src/accounts/` | Several isolated Pi accounts and pools, sign-in, model lists, health and limits. A session keeps the account it started with. |
9
+ | Sessions | `src/sessions/` | Pi SDK turns, run records and `events.jsonl`, interrupt, approvals, questions, recovery after a crash. |
10
+ | Prompt | `src/prompt/` | The system prompt compiled from sections with budgets and profiles; a stable prefix, and changing state delivered as updates in native history. |
11
+ | Memory | `src/memory/` | The Wiki: pages, facts, memory blocks, revisions, proposals with an evidence gate, undo, an Inbox, recall over past runs, and sleep-time consolidation. |
12
+ | Skills | `src/skills/` | Skills, prompts, and rules: a person's own in the data root, and built-in ones shipped with the harness or the application. |
13
+ | Plugins | `src/plugins/` | What a Pi session loads, chosen by the harness and never by Pi's own discovery; trust for a working folder's code; the list of every plugin and what reaches one caller. |
14
+ | Tools | `src/tools/` | Tool definitions, the memory tools, `ask_user`; a gate that asks the application's policy, then standing approvals, then the person. |
15
+ | Connected apps | `src/mcp/` | MCP connections, OAuth, per-session grants, redaction, a trace of what each call returned. |
16
+ | Terminal | `src/terminal/` | Interactive Pi sessions with isolated accounts, compiled instructions, a telemetry and tool extension, and the session socket with the `agentx-wiki` and `agentx-mcp` commands. |
17
+ | Server | `src/server/` | The same API over newline-delimited JSON, for a host that is not a Node program. |
18
+
19
+ It was split out of Xingular Agent, which is now an application built on it.
20
+
21
+ ## Using it from Node
22
+
23
+ ```ts
24
+ import { createHarness } from '@xingular/harness'
25
+
26
+ const harness = createHarness({
27
+ dataRoot: '/path/to/data', // knowledge and history: wiki/, registry/, runs/, index.sqlite
28
+ stateDir: '/path/to/state', // device-local: accounts, approvals, connections, sockets
29
+ productName: 'My App', // how the harness names itself to a model (default 'Xingular Harness')
30
+ secrets, // SecretStore; defaults to an owner-only file in stateDir
31
+ policy, // ToolPolicy: what a run may do, and what needs a person
32
+ directory, // names for the ids the application uses
33
+ builtins: ['/path/to/builtin'], // folders of built-in skills, prompts, and rules
34
+ plugins: [myPlugin] // the application's own parts, described as plugins
35
+ })
36
+
37
+ harness.sessions.provide((session) => myTools(session)) // the application's own tools
38
+ harness.runs.listen((event, run) => show(event)) // every run event, after it is saved
39
+
40
+ await harness.sessions.run({
41
+ run: { id, sessionId },
42
+ cwd, sessionDir, text,
43
+ caller: { agentId, agentName, actor: 'Chat' },
44
+ launch: { sections: [{ id: 'identity', title: 'Identity', body, budget: 2000, order: 10 }] },
45
+ tools: { allow: ['read', 'bash', 'my_tool'], gated: ['bash'] }
46
+ })
47
+ ```
48
+
49
+ ## How an application extends it
50
+
51
+ The harness knows a caller (an opaque id that owns memory), never an agent, a group, or a task.
52
+ An application adds what it knows through six points:
53
+
54
+ 1. **Prompt sections** (`Launch.sections`, `Launch.toolGroups`, `Launch.references`). Each
55
+ section has an `order` and may be `dynamic`. The harness adds its own (`blocks` 30, `rules` 40,
56
+ `references` 50, `skills` 60, `knowledge` 80, `tools` 90), applies budgets and the caller's
57
+ context profile, keeps stable sections in the system prompt, and sends dynamic ones as
58
+ updates.
59
+ 2. **Tool providers** (`harness.sessions.provide`). A provider returns `ToolSpec`s for a session;
60
+ only the names in the session's `allow` list are registered, in that order.
61
+ The harness provides the web tools itself: `webTools({ secrets })` gives `web_search` (Brave Search,
62
+ key read from the secret store) and `web_fetch` (checked reads of public pages). An application
63
+ passes it to `harness.sessions.provide` and switches the names on for each agent.
64
+ 3. **`ToolPolicy`**. `scope` says whether a call is inside what the run may do (or needs one
65
+ approval); `authorize` says whether the tool is still switched on; `approval` adds facts for
66
+ standing approvals; `connections` narrows the connected apps a session reaches.
67
+ 4. **`SecretStore`**. Where credentials live (a keychain, a vault). Keys are
68
+ `<account id>/<name>`.
69
+ 5. **`Directory`**. Names for the application's ids, shown in what the harness writes.
70
+ 6. **Plugin descriptions** (`HarnessOptions.plugins`). Each `PluginDescriptor` names one part of
71
+ the application and says which tools, prompt sections and tool groups it provides. It is data:
72
+ it changes what `plugins.list` and `plugins.inventory` report, never what runs. `WEB_PLUGIN`
73
+ describes the web tools. A host that is not a Node program sends them with `plugins.describe`.
74
+
75
+ `apps.expose(...)` additionally offers the application's tools through the connected-apps
76
+ broker (`mcp_list_tools`, `mcp_call_tool`), which is how a terminal session reaches them.
77
+
78
+ ## Plugins: what a session loads
79
+
80
+ A plugin is a bundle with a title, a description and contributions: instructions, skills,
81
+ commands, tools, connections, code (a Pi extension) and themes. A Pi package is a plugin as it
82
+ is; so are the harness's own parts (Memory, Wiki, History, Ask the user, Connected apps, Pi), the
83
+ application's, a person's registry ("Yours"), the folder every account shares, and what a
84
+ working folder brings.
85
+
86
+ - **The harness is the only loader.** A session opens Pi with its own discovery switched off and
87
+ the paths the harness chose (`Plugins.loading`); a terminal gets the same list as `--extension`,
88
+ `--skill`, `--prompt-template` and `--theme` flags after `--no-extensions --no-skills
89
+ --no-prompt-templates --no-themes`. The harness finds things with Pi's own resolver, which runs
90
+ no extension code. Pi still reads `AGENTS.md` and `SYSTEM.md` itself; they are listed.
91
+ - **Code waits for trust.** A working folder's `.pi/extensions` and the packages its settings
92
+ name are trusted together; each installed package is trusted on its own. Trust is a hash of the
93
+ files (`plugins.trust { hash }`), so the same code in another folder is trusted and a changed
94
+ file asks again. A loose file a person put in the shared `extensions/` folder loads without
95
+ asking. The first time this version runs, the packages already present are recorded as trusted,
96
+ once; anything installed or changed later waits.
97
+ - **Installing runs no code.** `plugins.install { source }` takes a folder, an npm name or a git
98
+ address and goes through Pi's own package manager on the settings every account shares, so
99
+ `pi list` agrees. Install scripts are switched off (`npm_config_ignore_scripts`). The package
100
+ arrives switched off and, with code, not trusted. `plugins.remove` and `plugins.update` take a
101
+ `pluginId`. A folder under `<data root>/plugins/` is found as a package where it is, and
102
+ removing it moves it to `<data root>/trash/plugins/`. `npm` and `git` in an account's Pi folder
103
+ are links to the shared folder, so one install serves every account.
104
+ - **Extensions have an interface without a terminal.** An SDK session that loaded an extension
105
+ binds it the way Pi's RPC mode does: `session_start` fires, and `select`, `confirm`, `input`
106
+ and `editor` become the run's question events; `notify`, `setStatus` and `setWidget` become
107
+ `extension.notice` run events. What needs a terminal (custom components, key handlers, themes)
108
+ does nothing. A tool an extension registered is offered to the model and asks before every
109
+ call. A command it registered runs when a message starts with it.
110
+ - **The gate runs last.** In an SDK session it is an inline extension, loaded after those on
111
+ disk; in a terminal the harness's extension is the last `--extension`. A plugin that changes a
112
+ tool call's input does so before the gate sees it.
113
+ - **A required part can be extended.** A package names the part it adds to and, optionally, a
114
+ hooks file: `"harness": { "extends": ["memory"], "hooks": "./hooks.mjs" }`. The harness runs
115
+ hooks itself (`src/plugins/hooks.ts`), in its own process, because most memory is written while
116
+ no session is open. `export default function (harness) { harness.on('memory.saved', async
117
+ (event, ctx) => …) }`; the events are `memory.saved`, `memory.block.changed`,
118
+ `wiki.page.saved` and `history.run.ended`. A hook is told after the write and cannot stop it,
119
+ has 30 seconds, and what it throws is kept in `plugin-hooks.json` and shown on its plugin. It
120
+ runs only while its plugin is switched on for everyone and trusted; what is loaded is checked
121
+ against that before each delivery. `ctx.call(connection, tool, args)` reaches a connected app
122
+ that is on for everyone, and only a tool marked to run without asking. A working folder's
123
+ package cannot bring a hook. An embedding application adds no hook events of its own yet.
124
+ - **An agent can propose a plugin, and only propose.** `plugin_propose` parks a set of files in
125
+ the Inbox. Accepting writes `<data root>/plugins/<name>/` with a `package.json` the harness
126
+ wrote, switched off and untrusted. No tool accepts, switches on or trusts.
127
+ - **Two plugins ship with the harness, switched off.** `output-trim` cuts a tool result longer
128
+ than its limit to its beginning and its end before the model reads it; `budget-guard` refuses
129
+ tool calls once a run passed a limit of model requests or tokens, leaves the agent one request
130
+ to wrap up, and ends the run if it calls tools again (`src/plugins/firstParty.ts`). They are
131
+ Pi extensions loaded inline, so they also run in unattended work and need no trust. A person
132
+ switches them like any plugin and sets their numbers with `plugins.configure { pluginId,
133
+ settings }`. A descriptor may declare `switchable`, `settings` and `code` to describe a part
134
+ like these. A terminal session does not load them yet.
135
+ - **A person can do the same from a terminal.** In a terminal session, `/plugins` lists what the
136
+ session loads and switches a plugin, or one thing it brings, for the session's project or for
137
+ everyone, and trusts code after showing its files. The session's shell can read the session
138
+ token from its environment, so a change is taken only with a second key that the harness's
139
+ extension claims once as it loads (`control.claim`) and keeps in its own memory; the socket's
140
+ `plugins.list` is open to the session, and `plugins.attach`, `plugins.detach`, `plugins.trust`
141
+ and `plugins.untrust` refuse without that key. A change applies when the session restarts.
142
+ Outside a session, `xingular-harness plugins list | show | install | remove | update | attach |
143
+ detach | trust | untrust` acts directly, as the person who typed it; `trust` shows the files
144
+ and wants a typed yes or `--yes`. It does not open the data root, so it settles no run.
145
+ - **Unattended runs and memory maintenance load no extension**, as before.
146
+ - **A person switches things on and off for a scope.** `plugins.attach` and `plugins.detach`
147
+ take a plugin's or a contribution's `id` and a `scope` (`{}` for everywhere, `{ agentId }`, or
148
+ `{ projectId }`). The narrowest scope that says something wins: an agent, then a project, then
149
+ everywhere. The choices are one file, `config/plugin-attachments.json`; registry enablement
150
+ (`skills.setEnabled`) reads and writes the same file, and the earlier `registry-enabled.json`
151
+ is read until the first change. A connected app keeps its scope in `connections.json` and is
152
+ switched through the same two methods. A switched-off extension, skill or prompt is not
153
+ loaded for that caller. The harness's own parts are required and refuse with their reason; an
154
+ application's parts are switched by the application. Switching something on grants no
155
+ permission: its tools still pass the gate.
156
+ - `plugins.list { cwd?, accountId?, agentId?, projectId? }` returns every plugin, with where each
157
+ is switched on (`attachment`); with an agent or a project, each state is what that caller
158
+ gets. `plugins.inventory { context, launch, tools, connections, cwd? }` returns what reaches
159
+ one caller, each item attributed to a plugin, with `unattributed` naming any prompt section,
160
+ tool group or tool no plugin accounts for.
161
+
162
+ A terminal session's `pi` command has four extensions of its own (`builtin:mcp`, `codemode`,
163
+ `tool-search`, `llama.cpp`). They are passed by name unless Pi's settings switch them off. An SDK
164
+ session does not have them.
165
+
166
+ ## Using it from another language
167
+
168
+ ```sh
169
+ npm run build
170
+ node dist/cli.js serve --state <dir> --data <dir> --name "My App" \
171
+ [--builtins <dir>] [--host-secrets] [--host-policy] [--host-tools] [--host-directory]
172
+ ```
173
+
174
+ Lines on stdin and stdout, one JSON object each:
175
+
176
+ ```
177
+ host → harness { v: 1, id, method, params } harness → host { v: 1, id, result | error }
178
+ harness → host { v: 1, event } a run event
179
+ harness → host { v: 1, notice } state changed: read it again
180
+ harness → host { v: 1, id, call, params } host → harness { v: 1, id, result | error }
181
+ ```
182
+
183
+ `harness.methods` lists every method; [`src/server/methods.ts`](src/server/methods.ts) is the
184
+ table. With the `--host-*` flags the harness calls the host for `secrets.get|set|delete`,
185
+ `policy.scope|authorize|approval|connections`, and `tools.call`; the host registers its tools
186
+ with `tools.register` and its names with `directory.set`.
187
+
188
+ `skills.import` accepts complete user-owned skill folders:
189
+
190
+ ```ts
191
+ { skills: [{ name, files: [{ path, content, encoding }] }] }
192
+ ```
193
+
194
+ It validates bounded relative paths and file encodings, requires a root
195
+ `SKILL.md`, rejects existing items, and writes the folders without enabling or executing them. It
196
+ has no knowledge of the application feature that may call it.
197
+
198
+ `npm run prove` starts that server on a throwaway data root, with no application present, and
199
+ exercises accounts, memory, skills, the prompt, connected apps, and a recorded session.
200
+
201
+ ## Run it as a standalone Pi terminal
202
+
203
+ After the CLI package is published to npm or your company's npm registry, install and launch it:
204
+
205
+ ```sh
206
+ npm install --global @xingular/harness
207
+ xingular-harness
208
+ ```
209
+
210
+ The machine needs Node 22.13 or newer and the `pi` command on `PATH`. On first launch, the
211
+ harness creates a `Default` Pi account; Pi handles provider sign-in.
212
+
213
+ To build and run from a source checkout instead:
214
+
215
+ ```sh
216
+ npm install
217
+ npm run build
218
+ node dist/cli.js
219
+ ```
220
+
221
+ This opens Pi's interactive terminal in the current working directory. The harness adds its
222
+ compiled context, isolated account, approval gate, Wiki commands, and terminal extension while
223
+ Pi provides the interface.
224
+
225
+ The default data and device state directories are `~/.xingular-harness/data` and
226
+ `~/.xingular-harness/state`. Override them with `--data <dir>` and `--state <dir>`. Session
227
+ history is stored per working directory and can be reopened with Pi's `/resume` command. The
228
+ explicit `terminal` command is an alias for the default launch. Use `--help` to see the commands;
229
+ `serve` remains the host-facing RPC service for integrations such as Xingular Agent/App.
230
+
231
+ ## Storage
232
+
233
+ ```
234
+ <data root>/
235
+ wiki/ home.md, library/, projects/<slug>/, facts/, blocks/, revisions, inbox, log
236
+ registry/ skills/, prompts/, rules/ (a person's own; never copied)
237
+ plugins/<name>/ packages kept with a person's data; each is found as an installed plugin
238
+ runs/<id>/ run.json, events.jsonl, context.json, caller.json
239
+ config/ context profiles, what is switched on for whom (plugin-attachments.json),
240
+ settings the harness reads
241
+ index.sqlite a rebuildable index of Wiki entries and runs (FTS5)
242
+ projects.json read only: the application owns it
243
+
244
+ <state dir>/
245
+ accounts/ registry, per-account Pi agent folders, audit log
246
+ approvals.json standing approvals
247
+ plugin-trust.json the code a person trusted, by hash, and whether earlier packages were taken over
248
+ plugin-registered.json what each extension registered the last time a session loaded it
249
+ plugin-hooks.json what each hooks file listens for and how its last run went
250
+ connections.json connected apps (secrets are in the SecretStore)
251
+ run/ the session socket and the terminal commands
252
+ ```
253
+
254
+ The files are the source of truth; `index.sqlite` can be deleted and rebuilt
255
+ (`harness.rebuildIndex()`).
256
+
257
+ ## Commands
258
+
259
+ ```sh
260
+ npm run typecheck
261
+ npm test # unit tests and the golden prompt/memory fixtures in tests/golden
262
+ npm run build # dist/cli.js (the xingular-harness command) and dist/terminal-pi.js
263
+ npm run prove # the standalone proof above
264
+ ```
265
+
266
+ Node 22.13 or newer (for `node:sqlite`).
267
+
268
+ ## Images
269
+
270
+ `SessionRunInput.requiresImageInput` optionally requires a vision-capable resolved Pi model before a prompt is delivered. The check includes direct `images` input, and the generic tool wrapper rejects image results on text-only models. Direct prompt images use Pi's `{ type: 'image', data, mimeType }` content shape. Applications own file formats, attachment selection, extraction and image preparation; the Harness only transports typed image content and validates model capability.