@hachej/boring-agent 0.3.2 → 0.4.0

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/README.md CHANGED
@@ -134,7 +134,7 @@ Choose the seam by how the text changes and who should see it:
134
134
 
135
135
  Failure is fail-open and Pi's: a throwing `context` is reported through `HarnessOptions.onReport` and the request goes out without the context (Pi's `hooks.each`, scheduler.js). An aborted turn rethrows. This is advisory text, not policy or authorization; do not use it to enforce a decision. The text is sent to the model provider and gateways or telemetry that log request bodies may see it; the model can quote it into a stored assistant message. Compaction summarizes the stored transcript only, so the context is not folded into summaries. After a crash the hook runs again at recovery, so the value is fresh per attempt, not replayed. Placement: before the round's question when that is the last message; after tool results or an assistant continuation, at the tail. Tests: `test/packages/agent-prompt-context.test.mjs`.
136
136
 
137
- Screen context: a browser can send what the person is looking at with each submit. `createRemoteChat({ requestContext })` adds the returned plain object to the submit body as `context`. In `ChatTransportAccess`, `requestContext(value)` receives it after validation and before `conversation.submit`; a value that is not a plain JSON object of at most 4 KB is `400 invalid-request`, and a host without `requestContext` ignores the field. The host stores `context` by conversation id in `requestContext` and renders it in `createPromptContext({ context })`; the transport keeps nothing. Tests: `test/packages/agent-chat-transport-context.test.mjs`, `test/packages/ui-remote-chat-context.test.mjs`.
137
+ Screen context: a browser can send what the person is looking at with each submit. `createRemoteChat({ requestContext })` adds the returned plain object to the submit body as `context`. In `ChatTransportAccess`, `requestContext(value)` receives it after validation and before `conversation.submit`; a value that is not a plain JSON object of at most 4 KB is `400 invalid-request`, and a host without `requestContext` ignores the field. The host stores `context` by conversation id in `requestContext` and renders it in `createPromptContext({ context })`; the transport keeps nothing. With `useRemoteChat` the context is the `requestContext` value as it is (`name`, `params`: a host written before placement keeps working) plus `placement` (`docked` or `floating`), reported by `PiChat` / `AmbientChat`; `PiChat` takes a `placement` prop for a host that floats it, as the `AgentLayer` does. `renderRequestContext(value)` (from `@hachej/boring-agent/prompt-context`) turns it into the agent's role and the screen for `createPromptContext`; placement changes behaviour, never authority (see [PAGE-TOOLS.md](../../docs/architecture/PAGE-TOOLS.md)). Tests: `test/packages/agent-chat-transport-context.test.mjs`, `test/packages/ui-remote-chat-context.test.mjs`, `test/packages/agent-prompt-context.test.mjs`, `test/contracts/chat-placement.test.mjs`, `test/contracts/chat-placement-registry.test.mjs`.
138
138
 
139
139
  ## Native questions
140
140
 
@@ -160,9 +160,9 @@ The native delivery task is version 2. Existing version 1 records migrate with e
160
160
 
161
161
  `resolveAccess` may return a Promise. When recovery reads the original actor from native producer input, pass that already-authenticated access as the sixth admission argument: `admit(tx, createProducer, target, taskOptions, context, access)`. Admission captures its identity before awaiting producer creation. Publication still resolves current access and compares the original identity. Omitting the sixth argument preserves resolution during admission; such a resolver must not read the same Harness or call `tx.task` after producer writes. Native transactions reject table reads after their first write. Explicit access is trusted host input, not authentication or a grant. Resolved credential fields are copied before digest, memo and provider awaits.
162
162
 
163
- Run `node examples/background-document.mjs` after building for fictional question, native producer and document delivery without a model, browser or shell. The example resolves a fixed fictional answer in trusted host code; it is not an authenticated remote human journey.
163
+ Run `node fixtures/background-document.mjs` after building for fictional question, native producer and document delivery without a model, browser or shell. The example resolves a fixed fictional answer in trusted host code; it is not an authenticated remote human journey.
164
164
 
165
- Import interfaces with `import type`. See [scaffold guide](../../docs/contracts/SCAFFOLD.md), [native examples](../../examples/native-compositions.ts) and [implementation evidence](../../docs/implementation/PARTIAL.md).
165
+ Import interfaces with `import type`. See [scaffold guide](../../docs/contracts/SCAFFOLD.md), [native examples](../../fixtures/native-compositions.ts) and [implementation evidence](../../docs/implementation/PARTIAL.md).
166
166
 
167
167
  ## Authorized conversation text
168
168
 
@@ -182,7 +182,7 @@ The adapter keeps the last bounded delivered window and no update queue. A slow
182
182
 
183
183
  TLS, cookies, CORS, socket/proxy lifecycle, browser presentation and deployment authentication remain host qualifications. Public projection tests exercise actual native watches through in-process Fetch/Web Streams. `npm run test:projection-consumer` installs the agent archive without files/UI/execution packages, checks strict declarations, repeats the tests and builds the receiver for a browser. It requires the pinned registry archives in a writable npm cache.
184
184
 
185
- Run `node examples/conversation-projection.mjs` after building for a fictional authenticated stream and idle revocation demonstration.
185
+ Run `node fixtures/conversation-projection.mjs` after building for a fictional authenticated stream and idle revocation demonstration.
186
186
 
187
187
  ## Authenticated question responses
188
188
 
@@ -194,7 +194,7 @@ Responses expose the narrow native decision, with `no-store` headers. An observe
194
194
 
195
195
  `test/packages/agent-question-response.test.mjs` exercises real native transactions through Fetch requests, including duplicate answers, identity/policy refusal, streamed byte limits, unacknowledged source cancellation and lost response recovery. `npm run test:question-consumer` repeats these scenarios from the installed agent and files tarballs without UI/execution packages; the handler reads bodies through `@hachej/boring-files/request-guard`, so install `@hachej/boring-files` (an optional peer) with this subpath. Set `npm_config_cache` to a writable cache of the pinned dependencies. Socket/browser authentication, remote inbox presentation and human identity-provider integration remain unqualified.
196
196
 
197
- The strict TypeScript consumer recipe additionally installs `@modelcontextprotocol/sdk@1.32.0`. Pinned Pi model declarations transitively load `@google/genai`, whose declarations reference that optional SDK. This dependency is needed for this `skipLibCheck: false` recipe even though the question runtime does not call MCP. The consumer does not establish strict checking with only the agent's declared peers installed.
197
+ The strict TypeScript consumer recipe additionally installs `@modelcontextprotocol/sdk@1.32.1`. Pinned Pi model declarations transitively load `@google/genai`, whose declarations reference that optional SDK. This dependency is needed for this `skipLibCheck: false` recipe even though the question runtime does not call MCP. The consumer does not establish strict checking with only the agent's declared peers installed.
198
198
 
199
199
  `@hachej/boring-agent/git` exports `createGitTool(repository?)`. Register this ordinary native tool with the same host-created `@hachej/boring-files/git` repository used by Bash or direct callers, or without one: each call then uses the `repository` of its own workspace binding (the one attached to the call's env, see [Workspace per call](#workspace-per-call)), so one tool serves a repository per person. It exposes the local service operations and preserves native ToolTask execution. The tool is always unsafe to replay because working Git has no publication receipts. The repository's host-bound authorization applies on every call. No environment or shell is mandatory for a file-only repository binding.
200
200
 
@@ -222,7 +222,7 @@ Native configuration retains tool names. A later registry replacement or native
222
222
 
223
223
  The resource must be UTF-8 `application/json`, optionally with `charset=utf-8`. The default accepted size is 65,536 bytes; `maxBytes` accepts integers from 1 to 1,048,576. This limits accepted bytes after the reader returns, not provider allocation. The loader preserves borrowed tool/provider ownership and checks an optional abort signal after awaited operations; it cannot interrupt a noncooperative reader or resolver.
224
224
 
225
- Run `node examples/agent-definitions.mjs` after building for a fictional definition, explicit native configuration and real document publication. `npm run test:definitions-consumer` installs files/agent archives with pinned registry dependencies, checks strict declarations and repeats exact-read, native publication, reopen and registry-replacement tests. Like the question consumer, this strict fixture explicitly installs the pinned optional model SDK declaration dependencies.
225
+ Run `node fixtures/agent-definitions.mjs` after building for a fictional definition, explicit native configuration and real document publication. `npm run test:definitions-consumer` installs files/agent archives with pinned registry dependencies, checks strict declarations and repeats exact-read, native publication, reopen and registry-replacement tests. Like the question consumer, this strict fixture explicitly installs the pinned optional model SDK declaration dependencies.
226
226
 
227
227
  ## Structured output and native tool evidence
228
228
 
@@ -33,8 +33,8 @@ async function body(request) {
33
33
  */
34
34
  function reads(conversation, context, project) {
35
35
  return {
36
- async entries(limit, cursor) {
37
- const page = await conversation.entries({}, limit, cursor, context);
36
+ async entries(query, limit, cursor) {
37
+ const page = await conversation.entries(query, limit, cursor, context);
38
38
  if (!project)
39
39
  return page;
40
40
  const record = await conversation.commit(tx => tx.conversation(conversation.id), context);
@@ -207,7 +207,19 @@ export function createChatTransportHandler(options) {
207
207
  return failure(400, 'invalid-request');
208
208
  }
209
209
  }
210
- const page = await read.entries(limit, cursor);
210
+ const entryId = (encoded) => {
211
+ if (encoded === null)
212
+ return undefined;
213
+ const value = Number(encoded);
214
+ if (!Number.isSafeInteger(value) || value < 1)
215
+ throw new RequestGuardError(400, 'Invalid native entry bound');
216
+ return value;
217
+ };
218
+ const maxEntryId = entryId(params.get('maxEntryId')), minEntryId = entryId(params.get('minEntryId'));
219
+ const order = params.get('order');
220
+ if (order !== null && order !== 'ascending' && order !== 'descending')
221
+ return failure(400, 'invalid-request');
222
+ const page = await read.entries({ ...(maxEntryId === undefined ? {} : { maxEntryId }), ...(minEntryId === undefined ? {} : { minEntryId }), ...(order === null ? {} : { order }) }, limit, cursor);
211
223
  if (access.revoked?.aborted)
212
224
  return failure(403, 'not-authorized');
213
225
  return json({ page });
@@ -22,4 +22,13 @@ export interface PromptContextOptions {
22
22
  */
23
23
  export declare function withRequestContext(messages: readonly Message[], text: string, tag?: string): Message[];
24
24
  export declare function createPromptContext(options: PromptContextOptions): Extension;
25
+ /**
26
+ * The text for the browser's request context: the screen's `{ name, params }` with `placement` beside them, as `useRemoteChat` sends it
27
+ * (an older client sends only `{ name, params }`, which still renders; a `{ screen }` wrapper is read too). The host calls this in
28
+ * `createPromptContext({ context })` on the value it stored by conversation id. `placement` is `docked` or `floating` and picks the
29
+ * agent's role. Both are advisory text from the browser: placement changes behaviour and never authority (a docked agent writes no
30
+ * more than a floating one), and anything else in the value is ignored.
31
+ * See docs/architecture/PAGE-TOOLS.md (PLACE-1..3).
32
+ */
33
+ export declare function renderRequestContext(value: unknown): string | undefined;
25
34
  //# sourceMappingURL=prompt-context.d.ts.map
@@ -36,3 +36,29 @@ export function createPromptContext(options) {
36
36
  })],
37
37
  });
38
38
  }
39
+ const ROLES = {
40
+ docked: 'The chat is docked beside the page: you lead. Open the pages, records and files that answer the question in the viewer, and say what you opened.',
41
+ floating: 'The chat floats over the page: the person leads. Do not move the page unless asked; answer briefly about what is on screen and suggest where to go.',
42
+ };
43
+ /**
44
+ * The text for the browser's request context: the screen's `{ name, params }` with `placement` beside them, as `useRemoteChat` sends it
45
+ * (an older client sends only `{ name, params }`, which still renders; a `{ screen }` wrapper is read too). The host calls this in
46
+ * `createPromptContext({ context })` on the value it stored by conversation id. `placement` is `docked` or `floating` and picks the
47
+ * agent's role. Both are advisory text from the browser: placement changes behaviour and never authority (a docked agent writes no
48
+ * more than a floating one), and anything else in the value is ignored.
49
+ * See docs/architecture/PAGE-TOOLS.md (PLACE-1..3).
50
+ */
51
+ export function renderRequestContext(value) {
52
+ if (typeof value !== 'object' || value === null)
53
+ return undefined;
54
+ const { placement, screen } = value;
55
+ const lines = [];
56
+ if (placement === 'docked' || placement === 'floating')
57
+ lines.push(`Placement: ${placement}. ${ROLES[placement]}`);
58
+ const shown = typeof screen === 'object' && screen !== null ? screen : value;
59
+ if (typeof shown.name === 'string') {
60
+ const { name, params } = shown;
61
+ lines.push(`The person is looking at: ${name}${params && typeof params === 'object' && Object.keys(params).length ? ` ${JSON.stringify(params)}` : ''}`);
62
+ }
63
+ return lines.length ? lines.join('\n') : undefined;
64
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-agent",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "type": "module",
@@ -139,8 +139,8 @@
139
139
  "@earendil-works/pi-durable": "~1.2.0",
140
140
  "@earendil-works/pi-ai": "~1.2.0",
141
141
  "@earendil-works/chord": "~1.2.0",
142
- "@hachej/boring-files": "^0.3.2",
143
- "@hachej/boring-ui-kit": "^0.3.2"
142
+ "@hachej/boring-files": "^0.4.0",
143
+ "@hachej/boring-ui-kit": "^0.4.0"
144
144
  },
145
145
  "peerDependenciesMeta": {
146
146
  "@hachej/boring-files": {