flowviant 0.125.2 → 0.125.3

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
@@ -223,5 +223,8 @@ this package version, builds, attests and attaches the binaries and manifest.
223
223
  R2 publication remains opt-in through `PUBLISH_R2` and the `r2` environment.
224
224
  For a manual upload run `sh cli/scripts/upload-binaries.sh` from the root; it
225
225
  checks all four binaries against the manifest and publishes `latest.json` last.
226
- `(cd cli && npm publish)` retains the prepublish parity gate and npm shrinkwrap.
226
+ `(cd cli && npm exec --yes --package=npm@11.17.0 -- npm publish)` retains
227
+ the prepublish parity gate and includes the npm shrinkwrap. The publisher is
228
+ pinned because [npm 12 removed shrinkwrap support](https://github.com/npm/cli/blob/latest/docs/lib/content/configuring-npm/package-lock-json.md#npm-shrinkwrapjson)
229
+ and omits it from tarballs; npm 12 consumers also ignore that lockfile.
227
230
  No sibling repository or separate app deploy key is required.
@@ -1,22 +1,28 @@
1
1
  /** Native gate; a full pool refuses only this spawn, preserving the turn. */
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { callOrchestrator } from '../orchestratorClient.mjs';
4
+ import { OrchestratorInputTooLarge, readOrchestratorInput } from '../orchestratorInput.mjs';
5
+ import { runningCompiledBinary } from '../launchCommand.mjs';
4
6
 
5
7
  export async function main() {
6
8
  try {
7
- let raw = ''; for await (const chunk of process.stdin) { raw += chunk; if (Buffer.byteLength(raw) > 64 * 1024) throw new Error('Hook input is too large.'); }
8
- const event = JSON.parse(raw);
9
+ const event = await readOrchestratorInput(process.stdin);
9
10
  const result = await callOrchestrator('hook', event);
10
11
  const output = result.deny ? { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: result.deny } }
11
12
  : result.updatedInput || result.context ? { hookSpecificOutput: { hookEventName: event.hook_event_name,
12
13
  ...(result.updatedInput ? { permissionDecision: 'allow', updatedInput: result.updatedInput } : {}),
13
14
  ...(result.context ? { additionalContext: result.context } : {}) } } : {};
14
- process.stdout.write(`${JSON.stringify(output)}\n`);
15
- } catch {
15
+ // __hook exits as soon as main returns. A large rewritten child prompt
16
+ // must reach the pipe before that exit, not stop partway through JSON.
17
+ await new Promise((resolve, reject) => process.stdout.write(`${JSON.stringify(output)}\n`, error => error ? reject(error) : resolve()));
18
+ } catch (error) {
16
19
  // Both CLIs interpret exit 2 as a tool refusal. A failed capability must
17
20
  // never let a native spawn escape the shared admission pool.
18
- process.stderr.write('The run’s orchestration gate is unavailable. Keep the work in the orchestrator until it reconnects.\n');
21
+ process.stderr.write(error instanceof OrchestratorInputTooLarge ? `${error.message}\n`
22
+ : 'The run’s orchestration gate is unavailable. Keep the work in the orchestrator until it reconnects.\n');
19
23
  process.exitCode = 2;
20
24
  }
21
25
  }
22
- if (process.argv[1] === fileURLToPath(import.meta.url)) await main();
26
+ // Bun bundles this module at the virtual entrypoint too. Only cli.mjs may
27
+ // dispatch it there; otherwise importing it consumes stdin before dispatch.
28
+ if (!runningCompiledBinary() && process.argv[1] === fileURLToPath(import.meta.url)) await main();
@@ -10,6 +10,7 @@ import { DAEMON_INSTANCE, FLEET_URL } from './config.mjs';
10
10
  import { runningCompiledBinary } from './launchCommand.mjs';
11
11
  import { scrubNativeText } from './runtimeEvents.mjs';
12
12
  import { scrub, learnExtraSecrets, forgetExtraSecrets } from './uplinkScrub.mjs';
13
+ import { OrchestratorInputTooLarge, readOrchestratorInput } from './orchestratorInput.mjs';
13
14
 
14
15
  const WAIT = 'Waiting for a free slot on the machine';
15
16
  // Codex 0.160 V2 serializes the namespace without punctuation in hook
@@ -224,11 +225,13 @@ export async function createOrchestrator({ turnId, runtime, trace, admit, reques
224
225
  const server = createServer(async (req, res) => {
225
226
  if (closed || req.method !== 'POST' || req.url !== '/' || !sameSecret(req.headers.authorization, `Bearer ${capability}`)) { res.writeHead(403).end(); return; }
226
227
  try {
227
- let raw = ''; for await (const chunk of req) { raw += chunk; if (Buffer.byteLength(raw) > 64 * 1024) throw new Error('Orchestration input is too large.'); }
228
- const { action, args } = JSON.parse(raw);
228
+ const { action, args } = await readOrchestratorInput(req);
229
229
  const result = await handle(action, args ?? {});
230
230
  res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }).end(JSON.stringify(result));
231
- } catch (error) { res.writeHead(409, { 'Content-Type': 'application/json' }).end(JSON.stringify({ error: text(error.message, 300) })); }
231
+ } catch (error) { res.writeHead(error instanceof OrchestratorInputTooLarge ? 413 : 409,
232
+ { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }).end(JSON.stringify({
233
+ error: text(error.message, 300), ...(error instanceof OrchestratorInputTooLarge ? { code: error.code } : {}),
234
+ })); }
232
235
  });
233
236
  server.requestTimeout = 5_000;
234
237
  await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); });
@@ -1,12 +1,16 @@
1
1
  import { createInterface } from 'node:readline';
2
2
  import { ORCHESTRATOR_TOOLS } from './orchestratorProtocol.mjs';
3
+ import { ORCHESTRATOR_REQUEST_BYTES, ORCHESTRATOR_INPUT_TOO_LARGE, OrchestratorInputTooLarge } from './orchestratorInput.mjs';
3
4
 
4
5
  export async function callOrchestrator(action, args) {
5
6
  const url = new URL(process.env.FLOWVIANT_ORCHESTRATOR_URL);
6
7
  if (url.hostname !== '127.0.0.1' || url.protocol !== 'http:') throw new Error('No held run is available.');
8
+ const body = JSON.stringify({ action, args });
9
+ if (Buffer.byteLength(body) > ORCHESTRATOR_REQUEST_BYTES) throw new OrchestratorInputTooLarge();
7
10
  const response = await fetch(url, { method: 'POST', headers: { Authorization: `Bearer ${process.env.FLOWVIANT_ORCHESTRATOR_CAPABILITY}`, 'Content-Type': 'application/json' },
8
- body: JSON.stringify({ action, args }), signal: AbortSignal.timeout(15_000) });
11
+ body, signal: AbortSignal.timeout(15_000) });
9
12
  const result = await response.json();
13
+ if (result.code === ORCHESTRATOR_INPUT_TOO_LARGE) throw new OrchestratorInputTooLarge();
10
14
  if (!response.ok) throw new Error(result.error ?? 'This run is no longer held.');
11
15
  return result;
12
16
  }
@@ -0,0 +1,47 @@
1
+ // A whole-event memory guard, aligned with the authenticated API's 24 MiB
2
+ // request budget. It is not an allowance on a prompt or tool-result field.
3
+ export const ORCHESTRATOR_REQUEST_BYTES = 24 * 1024 * 1024;
4
+ export const ORCHESTRATOR_INPUT_TOO_LARGE = 'ORCHESTRATOR_INPUT_TOO_LARGE';
5
+
6
+ export class OrchestratorInputTooLarge extends Error {
7
+ constructor() {
8
+ super('The orchestration hook request exceeds the 24 MiB transport limit.');
9
+ this.code = ORCHESTRATOR_INPUT_TOO_LARGE;
10
+ }
11
+ }
12
+
13
+ /** Read bounded raw bytes before decoding: pipe/HTTP chunks can split UTF-8.
14
+ * On refusal, drain without buffering so HTTP can still return its reason. */
15
+ export function readOrchestratorInput(stream) {
16
+ return new Promise((resolve, reject) => {
17
+ let chunks = [], bytes = 0;
18
+ const cleanup = () => {
19
+ stream.off('data', data); stream.off('end', end);
20
+ stream.off('error', fail); stream.off('close', closed);
21
+ };
22
+ const fail = error => {
23
+ cleanup(); chunks = [];
24
+ // The producer can abort after seeing the refusal. That late stream
25
+ // error must not crash the controller while the remainder is drained.
26
+ const ignore = () => {};
27
+ stream.on('error', ignore);
28
+ stream.once('close', () => stream.off('error', ignore));
29
+ stream.resume(); reject(error);
30
+ };
31
+ const closed = () => fail(new Error('The orchestration input closed before completion.'));
32
+ const data = chunk => {
33
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
34
+ bytes += buffer.length;
35
+ if (bytes > ORCHESTRATOR_REQUEST_BYTES) { fail(new OrchestratorInputTooLarge()); return; }
36
+ chunks.push(buffer);
37
+ };
38
+ const end = () => {
39
+ cleanup();
40
+ try { resolve(JSON.parse(Buffer.concat(chunks, bytes).toString('utf8'))); }
41
+ catch (error) { reject(error); }
42
+ chunks = [];
43
+ };
44
+ stream.on('data', data); stream.once('end', end);
45
+ stream.once('error', fail); stream.once('close', closed);
46
+ });
47
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowviant",
3
- "version": "0.125.2",
3
+ "version": "0.125.3",
4
4
  "description": "Run your own coding CLIs as build agents for Flowviant \u2014 Claude Code, Codex or Antigravity, on your own credentials. Holds your sessions, keeps a worktree per tab, and ships branches on your word.",
5
5
  "type": "module",
6
6
  "bin": {