@orkestrel/scaffold 0.0.69 → 0.0.71
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/dist/bin/main.js +8 -8
- package/dist/bin/main.js.map +1 -1
- package/dist/host/claude/agents/orkestrel.md +7 -7
- package/dist/host/guides/mcp.md +615 -63
- package/dist/host/guides/ollama.md +9 -8
- package/dist/host/guides/scaffold.md +19 -8
- package/dist/host/manifest.json +5 -5
- package/dist/src/core/index.cjs +11 -11
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.js +12 -12
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +4 -4
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.js +5 -5
- package/dist/src/server/index.js.map +1 -1
- package/package.json +8 -8
|
@@ -111,7 +111,7 @@ These invariants hold across `src/core` ↔ `ollama.md`. The engine contract its
|
|
|
111
111
|
|
|
112
112
|
1. **Doc ↔ source bijection.** Every `function` / `class` / `const` / `interface` / `type` row in the `## Surface` table is a real export of the `src/core` surface, and every export appears as a Surface row — exhaustive, both directions.
|
|
113
113
|
2. **Imports each boundary from its owner, no cycle.** `OllamaProvider` extends `AgentProvider` and implements `AgentProviderInterface`; that base, `ProviderRequest`, `ProviderIncrement`, `ProviderParserInterface`, `ProviderInterface`, `ProviderOptions`, and `Message` come from `@orkestrel/agent`. `createNDJSONParser` comes from `@orkestrel/ndjson`, `ToolCall` from `@orkestrel/tool`, `TokenUsage` from `@orkestrel/budget`, and the guards (`isRecord` / `isString` / `isNumber` / `parseJSONAs`) from `@orkestrel/contract`. The provider errors are owned by `@orkestrel/agent` and `ToolDefinition` by `@orkestrel/tool`: `src/core` imports neither, and both reach a caller through the base — the base throws the errors this wire's failures become, and it accepts the `ToolDefinition[]` a call advertises. Each dependency is one-way and never imports from `@src/core`. No module under `src/` imports `@orkestrel/timeout` — the base arms the deadline — and the manifest declares it as a development dependency for the guide's bounding pattern, which imports it as a consumer would.
|
|
114
|
-
3. **The `/api/chat` wire body.** `OllamaProvider` POSTs to `${url}${OLLAMA_CHAT_PATH}`, defaulting `url` to `DEFAULT_OLLAMA_URL` and `keep_alive` to `DEFAULT_KEEP_ALIVE`. The body carries `model`, the mapped `messages`, `stream: true` on every call, `keep_alive`, and `think` (the per-call `ProviderStreamOptions.think` override when present, else `OllamaOptions.think`, default `false`), adding `options` only when configured, `format` only when the call supplies a `ProviderStreamOptions.schema`, and `tools` only when a non-empty `ToolDefinition[]` is passed. Each tool maps to `{ type: 'function', function: { name } }`, with `description` and `parameters` added only when the corresponding `ToolDefinition` fields are supplied. Messages map to the wire's minimal `{ role, content }` turn, with `tool_calls` added only on a turn that replays them and `images` only on a multimodal turn. A non-OK status is the base's failure, not this package's: it throws `ProviderError` with code `'HTTP'`, the response `status`, and a message bounded to an excerpt of the response body.
|
|
114
|
+
3. **The `/api/chat` wire body.** `OllamaProvider` POSTs to `${url}${OLLAMA_CHAT_PATH}`, defaulting `url` to `DEFAULT_OLLAMA_URL` and `keep_alive` to `DEFAULT_KEEP_ALIVE`. The body carries `model`, the mapped `messages`, `stream: true` on every call, `keep_alive`, and `think` (the per-call `ProviderStreamOptions.think` override when present, else `OllamaOptions.think`, default `false`), adding `options` only when configured, `format` only when the call supplies a `ProviderStreamOptions.schema`, and `tools` only when a non-empty `ToolDefinition[]` is passed. Each tool maps to `{ type: 'function', function: { name } }`, with `description` and `parameters` added only when the corresponding `ToolDefinition` fields are supplied. `title` and `annotations` are **never sent**: the `/api/chat` tool function object carries no field for either. Messages map to the wire's minimal `{ role, content }` turn, with `tool_calls` added only on a turn that replays them and `images` only on a multimodal turn. A non-OK status is the base's failure, not this package's: it throws `ProviderError` with code `'HTTP'`, the response `status`, and a message bounded to an excerpt of the response body.
|
|
115
115
|
4. **Configurable `think` on the wire; the base's splitter keeps the assembled content clean.** For a thinking-capable model (for example `qwen3`) the per-request `think` flag is the only wire-level reasoning control (its native renderer honours neither the qwen3 `/no_think` token nor a Modelfile `PARAMETER think false`). It is configurable through `OllamaOptions.think` (default `false` — so the general-purpose provider stays immediate for non-thinking models and tests fast) and overrideable per call through `ProviderStreamOptions.think`. Set `think: true` when the caller displays reasoning separately from the answer: the daemon then separates reasoning natively, returning it on the distinct `message.thinking` channel, which `read` reports and the engine yields as `ProviderDelta` `{ channel: 'thinking', text }` and accumulates onto `ProviderResult.thinking`. Either way the base's `split` behaviour stays armed, because a daemon may ignore `think: false` for a thinking model and render reasoning inline as `<think>…</think>` content; `OllamaProvider` passes no `split` to `super`, so the default separation applies and the assembled `content` is the splitter's clean accumulation. The reasoning never re-enters the conversation.
|
|
116
116
|
5. **One NDJSON path.** Every call consumes NDJSON — one JSON object per `\n`-terminated line. `frame()` returns `createNDJSONParser()`, fresh per call, so no call inherits another's half-read record, and the base pairs it with a streaming `TextDecoder` so a record split across byte reads is reassembled. At end of input the base calls `finish(parser)`, which feeds a trailing `\n` through the parser: a non-conformant proxy's final unterminated line is recovered rather than silently dropped. `OllamaProvider` passes no `strict` to `super`, so a stream that ends without a settled record still assembles its result from the deltas it carried.
|
|
117
117
|
6. **`stream` yields deltas and returns the assembled result.** The engine yields each non-empty clean content delta as `{ channel: 'content', text }` and each daemon-side reasoning delta as `{ channel: 'thinking', text }`; its return value is the assembled `ProviderResult` whose `content` is the splitter's authoritative clean accumulation — the concatenation of the yielded content deltas, except across an implicit-open reclassification, where the reasoning prefix had already streamed before a bare `</think>` revealed it and the yields cannot be recalled — plus any tool calls collected across lines, the usage from the `done` line, and the separated `thinking` when the turn produced any.
|
|
@@ -121,7 +121,7 @@ These invariants hold across `src/core` ↔ `ollama.md`. The engine contract its
|
|
|
121
121
|
10. **The base owns everything that is not the wire.** The per-call deadline (`OllamaOptions.timeout`, `120_000`ms when omitted, folded with the caller's signal so either cancels the request and the timer is always cleared), the cancellation rule (a `stream` cancelled mid-flight throws `ProviderAbortError` carrying the partial, and the reader and parser are released in a `finally`), the transport seam (`OllamaOptions.fetch` and the per-request `OllamaOptions.headers` hook, awaited inside the deadline and raced against the combined signal), and the bounded error read all live in `AgentProvider`. `OllamaOptions` extends `ProviderOptions`, so those keys are the base's and behave identically for every provider built on it; [`agent.md`](agent.md) states each rule once.
|
|
122
122
|
11. **Context-framing `format` (provider-default cascade level, expose-only).** `OllamaOptions.format` is an optional `ContextFormat` (from `@orkestrel/agent`) — the provider's context-framing default, passed to `super` and exposed as `provider.format`. It is the provider-default level of the `AgentContext` build cascade (beats the managers' built-in framing, beaten by a manager-options or per-item override; see [`agent.md`](agent.md)), read by the Agent when it assembles the prompt. Omitted ⇒ `undefined` (framing-agnostic; core's built-in framing applies unchanged). It is **never sent on the `/api/chat` wire**: it is consumed by core, absent from the request `body`, and unrelated to Ollama's structured-output `format` parameter — that one is sent in the request `body`, but only when a per-call `ProviderStreamOptions.schema` is supplied — the framing default and that wire parameter only share a word.
|
|
123
123
|
12. **Event-free.** A pure functional boundary — no Emitter, no events. Each call is a function of its arguments.
|
|
124
|
-
13. **A core face that runs in a browser.** The package publishes the `.` entry alone, built from `src/core`, which imports no `node:*` module and no DOM global; the scoped core typecheck compiles it with the `ESNext` and `WebWorker` libraries and no ambient `@types`, so neither Node's globals nor the DOM's are in scope, and the base binds `globalThis.fetch` to its own receiver so a browser does not throw `Illegal invocation` on a bare transport reference.
|
|
124
|
+
13. **A core face that runs in a browser.** The package publishes the `.` entry alone, built from `src/core`, which imports no `node:*` module and no DOM global; the scoped core typecheck compiles it with the `ESNext` and `WebWorker` libraries and no ambient `@types`, so neither Node's globals nor the DOM's are in scope, and the base binds `globalThis.fetch` to its own receiver so a browser does not throw `Illegal invocation` on a bare transport reference. [`tests/service/page.test.ts`](../tests/service/page.test.ts) gates that claim rather than recording it: it launches the host's own Chrome or Edge through `@orkestrel/browser`, serves the built `@orkestrel/ollama` core entry and its whole `@orkestrel` import closure from an import map on a `127.0.0.1` fixture, and reads the page's requests from the browser's own request log. The page loads with no uncaught error and no console error, on the recorders a deliberate page fault then makes report; an `Agent` holding a page-defined DOM-writing `Tool` completes a tool loop against the live daemon through a same-origin `createRelay` server, where the model's recorded call reaches the page, the tool's minted receipt is read back out of the real DOM and found again as the `role: 'tool'` message of the relay request and the `/api/chat` request that follow the turn the call was dispatched in, and the run settles with `partial: false`; the page issues exactly one `POST /inference` per turn and nothing else across the whole operation window, with a deliberate `/control` request proving such a window can report one; a wrong bearer arrives as `ProviderError` with code `'HTTP'` and status `401` with no daemon request made; and `createOllama` in the page answers a prompt over `/api/chat` on the daemon's own origin after `requireDaemonOrigin` confirms the daemon permits the page's origin. Each attempt ends within its allowance plus its release share, so nothing the attempt schedules outlasts the case that holds it. The attempt's signal cancels the part of the connection the installed `BrowserOptions.signal` races — discovery, the port-free check, the launch, and `client.connect()` — and the attempt's own race is what bounds every phase after that, the target listing the connection ends with included; [`tests/service/page.test.ts`](../tests/service/page.test.ts) and the `PAGE_BOUNDS` table it spends state the mechanism. The model's choice to call a tool is retried at most 3 times, and that choice is the only reading another attempt buys: every attempt retains its observations before any page read can throw, and only a run that completed an answer without dispatching the tool spends another launch. An acquisition, read, release, or run failure ends the retry, and so does a run that deadline interrupted before it settled — an interrupted run reports no tool call for a reason another launch cannot clear, so retrying it would report the exhaustion as the model never choosing the tool. The request accounting is asserted over each retained attempt, so what the gate establishes is that the model called the tool within those attempts and that no attempt's traffic went unread. The daemon is the remaining limit — a page reaches `/api/chat` directly only where the daemon, or a proxy in front of it, answers the page's origin; otherwise relay the call through your own server ([Relaying through your own server](#relaying-through-your-own-server)).
|
|
125
125
|
14. **Tested live against a real local Ollama (no `skipIf`).** The live tests run against a real Ollama daemon — no mocks, only genuine third-party calls — model `qwen3.5:2b-q4_K_M`, with `OLLAMA_HOST` / `OLLAMA_MODEL` overridable. Unlike the other surfaces, the dedicated `service` project requires the daemon: `tests/setupService.ts` throws a clear error if it is unreachable and warms the model (a `num_predict: 1` chat) before the suite, so the live tests run unconditionally (no `describe.skipIf`). The project runs serially (`fileParallelism: false`) with a 120s test/hook timeout so a cold load cannot flake it; `keep_alive` keeps the model resident across files, and `bash scripts/ollama.sh` brings the daemon and model up before the battery in CI. Assertions are structural — they hold whatever wording a small model produces — and never pin exact output. The hermetic half sits in `tests/src/core/`, and the relay round trip is proved hermetically in [`tests/src/core/integration.test.ts`](../tests/src/core/integration.test.ts) and live in [`tests/service/relay.test.ts`](../tests/service/relay.test.ts).
|
|
126
126
|
15. **Doc ↔ source method bijection.** The `## Methods` table lists exactly the members `OllamaProvider` declares — `frame`, `body`, `read`, and `finish` — and the class declares exactly those. `generate` and `stream` reach a caller through the same class but are declared once, in `AgentProvider`, and documented in [`agent.md`](agent.md).
|
|
127
127
|
|
|
@@ -198,7 +198,7 @@ provider.finish(parser) // [] — the parser held nothing back
|
|
|
198
198
|
|
|
199
199
|
### Running in the browser
|
|
200
200
|
|
|
201
|
-
The published face is core, so the same `createOllama` call runs unchanged in a browser module. Point `url` at whatever the page can actually reach: the daemon's own origin where it answers that origin, or your own server standing in front of it. A page that reaches a daemon directly is a development arrangement — it puts the model endpoint on the network the page runs on — so prefer one of the server patterns that follow for anything a user runs.
|
|
201
|
+
The published face is core, so the same `createOllama` call runs unchanged in a browser module. Point `url` at whatever the page can actually reach: the daemon's own origin where it answers that origin, or your own server standing in front of it. A page that reaches a daemon directly is a development arrangement — it puts the model endpoint on the network the page runs on — so prefer one of the server patterns that follow for anything a user runs. The daemon decides the page's origin before the call: a browser sends an `OPTIONS` preflight to `/api/chat` first, and a daemon that does not permit that origin refuses it, so set `OLLAMA_ORIGINS` to include the origin the page is served from. [`tests/service/page.test.ts`](../tests/service/page.test.ts) drives this arrangement for real, and gates it behind that same preflight.
|
|
202
202
|
|
|
203
203
|
```ts
|
|
204
204
|
import { createOllama } from '@orkestrel/ollama'
|
|
@@ -259,7 +259,7 @@ const relayed = await browser.generate(messages, abort.signal)
|
|
|
259
259
|
relayed.content // the daemon's answer, reassembled from the relay's frames
|
|
260
260
|
```
|
|
261
261
|
|
|
262
|
-
A refusal never becomes a frame: a wrong credential reaches the browser as a `ProviderError` with code `'HTTP'` and status `401`, and the provider on the server is never entered. [`agent.md`](agent.md) states the frame vocabulary, the byte limit, and the rest of the refusal statuses. A page calling the relay from another origin needs CORS permission headers the relay route does not send, so serve the page from the relay server's own origin (
|
|
262
|
+
A refusal never becomes a frame: a wrong credential reaches the browser as a `ProviderError` with code `'HTTP'` and status `401`, and the provider on the server is never entered. [`agent.md`](agent.md) states the frame vocabulary, the byte limit, and the rest of the refusal statuses. A page calling the relay from another origin needs CORS permission headers the relay route does not send, so serve the page from the relay server's own origin (which is what [`tests/service/page.test.ts`](../tests/service/page.test.ts) does, from one loopback fixture serving the page, its modules, and the route) or put an origin-checking, CORS-answering middleware in front of the route (see the server guide), naming the `OPTIONS` preflight the browser sends with `authorization` and `content-type` as the requested headers.
|
|
263
263
|
|
|
264
264
|
### Routing through your own server (obfuscated tokens)
|
|
265
265
|
|
|
@@ -348,16 +348,16 @@ A cancel is outside that taxonomy: the caller's signal and the deadline both thr
|
|
|
348
348
|
|
|
349
349
|
## Tests
|
|
350
350
|
|
|
351
|
-
The hermetic projects — `src:core`, `setup`, `guides`, `conformance`, `policy`, and `config` — run with no daemon and no network. The `service` project requires a warm local Ollama, and `distribution` packs and installs the artifact.
|
|
351
|
+
The hermetic projects — `src:core`, `setup`, `guides`, `conformance`, `policy`, and `config` — run with no daemon and no network. The `service` project requires a warm local Ollama and, for the page proof, a Chromium-family browser and a built `dist`; `distribution` packs and installs the artifact.
|
|
352
352
|
|
|
353
353
|
- [`tests/guides.test.ts`](../tests/guides.test.ts) — the `## Surface` ↔ `src/core` bijection (value + type exports), the `OllamaProvider` method bijection, and the equality gate: every `Summary` cell against its declaration's description paragraph, the titled `createOllama + generate` fence against the `@example` block of that title (pinned so the titled pair cannot be retired silently), and the README pitch against this guide's tagline. It also runs the flagship fences and asserts the values their comments claim.
|
|
354
354
|
- [`tests/src/core/OllamaProvider.test.ts`](../tests/src/core/OllamaProvider.test.ts) — hermetic provider request-shape, framing, transport-seam, abort, deadline, and unreachable-upstream coverage, plus the streaming fold — deltas, tool-call accumulation, done-line usage, the unterminated tail flush, and think separation — driven over a canned NDJSON transport. Its recording proxy uses a deliberately unreachable default and asserts only captured requests and local provider behavior.
|
|
355
355
|
- [`tests/src/core/helpers.test.ts`](../tests/src/core/helpers.test.ts) — the wire leaves: the request projection and every response extraction, including each malformed-wire degradation.
|
|
356
356
|
- [`tests/src/core/factories.test.ts`](../tests/src/core/factories.test.ts) — hermetic `createOllama` shape, identity, defaults, passthrough, and unreachable-upstream coverage.
|
|
357
357
|
- [`tests/src/core/integration.test.ts`](../tests/src/core/integration.test.ts) — the `src/core` integration scope. A real `@orkestrel/server` + `@orkestrel/router` relay in front of this provider carries ordered content and thinking deltas, tools, and usage while keeping the browser and daemon credentials apart, refuses a wrong bearer with an empty `401` before the daemon transport is entered, crosses a browser cancel and a server deadline as an abort frame with its partial, reduces a daemon stream failure to the fixed public error frame, and replays a returned tool call. Beside those, hermetic context framing, recap, reference, cherry-pick, canonical assembly order, scope allow-lists, workspace injection, and conversation selection run through the unreachable recording proxy, with a compile-time drift gate asserting the wire request and response shapes stay compatible with the official `ollama` client's types; that gate's authoritative check is `npm run check` (root tsc), while its run under vitest is an incidental no-op (`expectTypeOf` performs no runtime assertions).
|
|
358
|
-
- [`tests/setup.test.ts`](../tests/setup.test.ts) — the shared host-independent test infrastructure, over the conversation padding, the throwing and recording summarizers,
|
|
359
|
-
- [`tests/setupServer.test.ts`](../tests/setupServer.test.ts) — the Node-resource half of `tests/setupServer.ts`: the recording proxy and the relay server on real loopback sockets, the captured and open transports, whose fixtures answer in memory, the capture wait, the provider-stream driver,
|
|
360
|
-
- [`tests/setupService.test.ts`](../tests/setupService.test.ts) — the hermetic half of the `service` project's setup: the environment readers, the sampling tables, and the
|
|
358
|
+
- [`tests/setup.test.ts`](../tests/setup.test.ts) — the shared host-independent test infrastructure, over the conversation padding, the throwing and recording summarizers, the workspace seeder, and the `PAGE_TOOL` definition `tests/setup.ts` exports, and over the host-independent half of `tests/setupServer.ts`: its request-narrowing guards, its refusing and streaming transports, its tool fixtures, its scripted agent stream and agent-stream driver, and its environment readers.
|
|
359
|
+
- [`tests/setupServer.test.ts`](../tests/setupServer.test.ts) — the Node-resource half of `tests/setupServer.ts`: the recording proxy and the relay server on real loopback sockets, the captured and open transports, whose fixtures answer in memory, the capture wait, the provider-stream driver, the wire tables `WEATHER_TOOL` and the insatiable tool's chunk line, and the page fixture's Node half — the request recorder the relay server and the page fixture share, the derived import map, the served document's presence guards, the contained file route, the control route, the port reservation, the membership the page bounds declare — the attempt allowance, the release share beside it, the per-call shares spent inside them, the case bound that contains the pair, and the launch count the retry spends — the ordering the live controls' own intervals hold, the attempt deadline's own expiry error, the elapsed completion a bounded attempt reports on each interleaving that can outlast it (an acquisition that never settles, one that settles inside the release share, a release that parks past that share, and a release that crossed the allowance), each of those measured against the deadlines it had to wait out plus one named scheduling allowance, so what the case proves is timer-bounded completion rather than a nominal sum, the failure each of them keeps reachable as its cause — the attempt's own failure under a composed strand, and the deadline's `TimeoutError` under a crossed release — the reading each thrown failure carries into a composed message, the decision the bounded retry's predicate makes over an inert attempt record, including the interrupted run it refuses, the release order a real fixture takes when the release after it rejects, the evaluate boundary's narrowing and the deadline it carries, and the guards that narrow what the page reports back.
|
|
360
|
+
- [`tests/setupService.test.ts`](../tests/setupService.test.ts) — the hermetic half of the `service` project's setup: the environment readers, the sampling tables, the readiness contract, and each live page gate's refusal — a host with no browser, a tree with no build, and a daemon that refuses the page's origin.
|
|
361
361
|
- [`tests/conformance.test.ts`](../tests/conformance.test.ts) — where this package's wire types drift from the official `ollama` client they are written against.
|
|
362
362
|
- [`tests/distribution.test.ts`](../tests/distribution.test.ts) — the packed package installed into a throwaway consumer: the exports map, the shipped declarations, and the module objects a real runtime hands that consumer.
|
|
363
363
|
- [`tests/policy.test.ts`](../tests/policy.test.ts) and [`tests/config.test.ts`](../tests/config.test.ts) — the vendored cross-cutting proofs: the path- and text-shaped repository laws, and that the root configuration resolves its aliases, projects, and outputs.
|
|
@@ -365,6 +365,7 @@ The hermetic projects — `src:core`, `setup`, `guides`, `conformance`, `policy`
|
|
|
365
365
|
- [`tests/service/factories.test.ts`](../tests/service/factories.test.ts) — live `createOllama` generation and streaming coverage.
|
|
366
366
|
- [`tests/service/relay.test.ts`](../tests/service/relay.test.ts) — the browser-to-server relay against the live daemon: a generated answer through the authenticated route, streamed deltas joining to the settled content, and a cancel after a live delta returning the relay's partial.
|
|
367
367
|
- [`tests/service/transport.test.ts`](../tests/service/transport.test.ts) — the transparent wire proxy, end-to-end. A `createRecordingProxy(OLLAMA_CONFIG.host)` — a real `@orkestrel/server` + `@orkestrel/router` HTTP server — records the inbound request, forwards it to the selected Ollama service, and streams the response back.
|
|
368
|
+
- [`tests/service/page.test.ts`](../tests/service/page.test.ts) — the live page receipt, in a real Chrome or Edge that `@orkestrel/browser` launches against a `127.0.0.1` fixture serving the page, the installed `@orkestrel` module tree, this workspace's own build, a `/control` route, and the authenticated relay from one origin. It proves that the published closure evaluates in the page with the page error recorder and the console recorder quiet, that the page parks exactly the operation table this proof evaluates by name, read back out of the running browser, that an `Agent` holding a page-defined DOM-writing `Tool` completes a tool loop against the live daemon over the same-origin relay and carries the minted receipt into the next turn's `role: 'tool'` message on the relay wire and the daemon wire, that the browser log, the page's Resource Timing drain, and the fixture's own record each report a request the page deliberately made, and the page error recorder and the console recorder each report a fault the page deliberately produced, that a wrong bearer is refused before the daemon is contacted, and that `createOllama` in the page answers a prompt on the daemon's own origin. Controls beside those drive a real browser through each interleaving the attempt bound covers, and assert the elapsed time of the whole call against the allowance plus the release share every time: an allowance too short to finish reads the attempt's own expiry error back rather than an inner browser timeout; an allowance the observation outlasts leaves the session released; an acquisition that settles after the allowance is released rather than abandoned, and is never reported as stranded; and an attempt whose release crossed the allowance reports that release rather than the answer the observation had already returned. Every precondition is a hard throw naming its fix — the daemon and model from `tests/setupService.ts`, the browser from `requirePageBrowser`, the build from `requireBuild`, and the daemon's permission for the page origin from `requireDaemonOrigin` — so a host missing one fails the project rather than skipping.
|
|
368
369
|
- [`tests/service/tools.test.ts`](../tests/service/tools.test.ts) and [`tests/service/authority.test.ts`](../tests/service/authority.test.ts) — live tool dispatch through the agent loop, and the authority gate's approval and denial paths.
|
|
369
370
|
- [`tests/service/budget.test.ts`](../tests/service/budget.test.ts) and [`tests/service/lifecycle.test.ts`](../tests/service/lifecycle.test.ts) — live token-budget enforcement mid-stream, and the agent's status, event, and abort lifecycle.
|
|
370
371
|
- [`tests/service/schema.test.ts`](../tests/service/schema.test.ts) and [`tests/service/scopes.test.ts`](../tests/service/scopes.test.ts) — a live structured-output `schema` constraining the answer, and a live scope filtering what reaches the wire.
|
|
@@ -1369,8 +1369,9 @@ leaf and its proof, and the byte-identical root dotfiles. The session-start hook
|
|
|
1369
1369
|
split by job. The bench probe reports whether a bench CLI resolves, and the dependency hook installs
|
|
1370
1370
|
the lockfile's closure in a remote session.
|
|
1371
1371
|
The Ollama hook invokes `scripts/ollama.sh` only when `CLAUDE_CODE_REMOTE=true`; direct invocation
|
|
1372
|
-
remains available for live-service setup.
|
|
1373
|
-
|
|
1372
|
+
remains available for live-service setup. Claude Code Cloud is Linux and has bash: SessionStart
|
|
1373
|
+
runs that POSIX script and never a Windows wrapper. What wires a bench stays in the canon, and a
|
|
1374
|
+
session reads it at its primary root.
|
|
1374
1375
|
|
|
1375
1376
|
`scripts/ollama.sh` defaults to `http://127.0.0.1:11434` and `qwen3.5:2b-q4_K_M`. It requires Node
|
|
1376
1377
|
for native URL and JSON handling and curl for the HTTP protocol. The script accepts an HTTP or HTTPS
|
|
@@ -1383,12 +1384,22 @@ completion.
|
|
|
1383
1384
|
|
|
1384
1385
|
When an HTTP loopback endpoint is unreachable, the script may start an installed Ollama executable
|
|
1385
1386
|
in an owned POSIX process group. A failure sends that owned group `TERM`, then sends `KILL` if it
|
|
1386
|
-
does not stop within 5 seconds; a reused daemon remains untouched. Direct reuse works from Git Bash
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1387
|
+
does not stop within 5 seconds; a reused daemon remains untouched. Direct reuse works from Git Bash
|
|
1388
|
+
on Windows, but local startup there fails because Bash cannot safely terminate the Windows process
|
|
1389
|
+
tree. Automatic installation is limited to Linux cloud or CI automation. The official installer
|
|
1390
|
+
download follows only HTTPS redirects, must be nonempty, and runs within the remaining setup
|
|
1391
|
+
deadline. The installer may require root or `sudo`, and its own platform prerequisites remain
|
|
1392
|
+
authoritative. The full setup deadline is 590 seconds, including a 60-second local startup
|
|
1393
|
+
allowance, within the hook's 600-second timeout.
|
|
1394
|
+
|
|
1395
|
+
Windows test helpers run the HTTP setup protocol in process. For local startup, they resolve a
|
|
1396
|
+
regular-file Ollama executable through `@orkestrel/process` and launch it with `serve`, without
|
|
1397
|
+
Bash. An absent executable returns exit `127` when the endpoint needs local startup; a reachable
|
|
1398
|
+
daemon needs no executable. Windows fixture protocol tests run without an installed executable.
|
|
1399
|
+
The native `serve` launch skips when `resolveExecutable` finds no regular-file Ollama. Fixture
|
|
1400
|
+
setup uses an isolated PATH so an unready fixture cannot launch the host daemon. Local hooks
|
|
1401
|
+
succeed without making requests; remote hooks run setup. Claude Code Cloud continues to invoke
|
|
1402
|
+
`scripts/ollama.sh`.
|
|
1392
1403
|
|
|
1393
1404
|
`CANON_PATHS` is the instruction canon, staged for reading instead: the `AGENTS.md` coding contract,
|
|
1394
1405
|
the `CLAUDE.md` harness bridge, the `.agents/orchestration.md` agent-operation contract, the rules
|
package/dist/host/manifest.json
CHANGED
|
@@ -352,7 +352,7 @@
|
|
|
352
352
|
"storage": "claude/agents/orkestrel.md",
|
|
353
353
|
"destination": ".claude/agents/orkestrel.md",
|
|
354
354
|
"executable": false,
|
|
355
|
-
"digest": "
|
|
355
|
+
"digest": "761ad0c795de9afd5c7dfa455c6b500cf18f278a6f0eae89b5370ce469a6d12b"
|
|
356
356
|
},
|
|
357
357
|
{
|
|
358
358
|
"storage": "claude/agents/planner.md",
|
|
@@ -802,7 +802,7 @@
|
|
|
802
802
|
"storage": "guides/mcp.md",
|
|
803
803
|
"destination": "guides/mcp.md",
|
|
804
804
|
"executable": false,
|
|
805
|
-
"digest": "
|
|
805
|
+
"digest": "8cc580a9819d11771b585e8a915fcc6b7bd3ff4a575b6c6482cd76c501367664"
|
|
806
806
|
},
|
|
807
807
|
{
|
|
808
808
|
"storage": "guides/middleware.md",
|
|
@@ -826,7 +826,7 @@
|
|
|
826
826
|
"storage": "guides/ollama.md",
|
|
827
827
|
"destination": "guides/ollama.md",
|
|
828
828
|
"executable": false,
|
|
829
|
-
"digest": "
|
|
829
|
+
"digest": "cec4bcaa69a0f8e191decff335f3c431b9ddf0e11d29cd6ff6368c46e1108b0f"
|
|
830
830
|
},
|
|
831
831
|
{
|
|
832
832
|
"storage": "guides/pool.md",
|
|
@@ -892,7 +892,7 @@
|
|
|
892
892
|
"storage": "guides/scaffold.md",
|
|
893
893
|
"destination": "guides/scaffold.md",
|
|
894
894
|
"executable": false,
|
|
895
|
-
"digest": "
|
|
895
|
+
"digest": "a3253b032e2f7de815c34ec6cbd111679a8b222c60780e44ade0550a548f5f1c"
|
|
896
896
|
},
|
|
897
897
|
{
|
|
898
898
|
"storage": "guides/sea.md",
|
|
@@ -1993,5 +1993,5 @@
|
|
|
1993
1993
|
]
|
|
1994
1994
|
}
|
|
1995
1995
|
],
|
|
1996
|
-
"digest": "
|
|
1996
|
+
"digest": "9f8770d4e8f9bc8ecace8c5b1b4e4ac285a4ad6f33aae7559ddacb84a403597b"
|
|
1997
1997
|
}
|
package/dist/src/core/index.cjs
CHANGED
|
@@ -4,7 +4,7 @@ let _orkestrel_template = require("@orkestrel/template");
|
|
|
4
4
|
let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
5
5
|
var package_default = {
|
|
6
6
|
name: "@orkestrel/scaffold",
|
|
7
|
-
version: "0.0.
|
|
7
|
+
version: "0.0.71",
|
|
8
8
|
description: "Scaffold workspaces with commands: new, audit, repair, catalog, and overwrite.",
|
|
9
9
|
keywords: [
|
|
10
10
|
"audit",
|
|
@@ -90,19 +90,19 @@ var package_default = {
|
|
|
90
90
|
"prepublishOnly": "npm run format:check && npm run lint:check && npm run check && npm run build && npm test && npm run test:distribution -- --mode release"
|
|
91
91
|
},
|
|
92
92
|
dependencies: {
|
|
93
|
-
"@orkestrel/console": "^0.0.
|
|
93
|
+
"@orkestrel/console": "^0.0.14",
|
|
94
94
|
"@orkestrel/contract": "^0.0.17",
|
|
95
95
|
"@orkestrel/emitter": "^0.0.10",
|
|
96
|
-
"@orkestrel/markdown": "^0.0.
|
|
97
|
-
"@orkestrel/process": "^0.0.
|
|
98
|
-
"@orkestrel/template": "^0.0.
|
|
96
|
+
"@orkestrel/markdown": "^0.0.15",
|
|
97
|
+
"@orkestrel/process": "^0.0.13",
|
|
98
|
+
"@orkestrel/template": "^0.0.8"
|
|
99
99
|
},
|
|
100
100
|
devDependencies: {
|
|
101
101
|
"@microsoft/api-extractor": "^7.59.1",
|
|
102
102
|
"@orkestrel/guide": "^0.0.19",
|
|
103
|
-
"@orkestrel/html": "^0.0.
|
|
104
|
-
"@orkestrel/probe": "^0.0.
|
|
105
|
-
"@orkestrel/test": "^0.0.
|
|
103
|
+
"@orkestrel/html": "^0.0.10",
|
|
104
|
+
"@orkestrel/probe": "^0.0.15",
|
|
105
|
+
"@orkestrel/test": "^0.0.15",
|
|
106
106
|
"@types/node": "^26.5.1",
|
|
107
107
|
"@vitest/browser-playwright": "^4.1.11",
|
|
108
108
|
"oxfmt": "^0.68.0",
|
|
@@ -2851,7 +2851,7 @@ var ScaffoldError = class extends Error {
|
|
|
2851
2851
|
* ```
|
|
2852
2852
|
*/
|
|
2853
2853
|
function isScaffoldError(value) {
|
|
2854
|
-
return value
|
|
2854
|
+
return (0, _orkestrel_contract.isInstance)(value, ScaffoldError);
|
|
2855
2855
|
}
|
|
2856
2856
|
//#endregion
|
|
2857
2857
|
//#region src/core/validators.ts
|
|
@@ -5791,7 +5791,7 @@ function replaceManifestRanges(manifest, pins) {
|
|
|
5791
5791
|
if (manifest.charAt(value) === ":") {
|
|
5792
5792
|
value += 1;
|
|
5793
5793
|
while (value < manifest.length && /\s/u.test(manifest.charAt(value))) value += 1;
|
|
5794
|
-
if (
|
|
5794
|
+
if ((0, _orkestrel_contract.isString)(key) && sectionNames.has(key) && manifest.charAt(value) === "{") {
|
|
5795
5795
|
let sectionDepth = 0;
|
|
5796
5796
|
let sectionCursor = value;
|
|
5797
5797
|
while (sectionCursor < manifest.length) {
|
|
@@ -5873,7 +5873,7 @@ function replaceManifestRanges(manifest, pins) {
|
|
|
5873
5873
|
}
|
|
5874
5874
|
valueStart += 1;
|
|
5875
5875
|
while (valueStart < section.length && /\s/u.test(section.charAt(valueStart))) valueStart += 1;
|
|
5876
|
-
if (
|
|
5876
|
+
if (!(0, _orkestrel_contract.isString)(key) || section.charAt(valueStart) !== "\"") {
|
|
5877
5877
|
sectionCursor = keyEnd;
|
|
5878
5878
|
continue;
|
|
5879
5879
|
}
|