@holokai/neuron-sdk 0.4.0 → 0.5.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.
package/README.md CHANGED
@@ -843,69 +843,75 @@ The `auth` callback isn't returning a valid token. If using a refresh flow, ensu
843
843
 
844
844
  ## Releasing (maintainers)
845
845
 
846
- `@holokai/neuron-sdk` is published to public npm. Releases are cut from a
847
- monorepo-scoped git tag, and CI does the publish — there is no npm token on
848
- anyone's laptop and none stored in the repo.
846
+ `@holokai/neuron-sdk` is published to public npm by BigBrain's Release
847
+ workflow (`.github/workflows/release.yml`). There is no npm token on anyone's
848
+ laptop and none stored in the repo, and pushing a tag publishes nothing.
849
+
850
+ ### Pick the version
851
+
852
+ Follow semver:
853
+
854
+ - Pilot phase, we are in **`0.x`**. Because the protocol is still absorbing
855
+ pilot-developer feedback, a **breaking** change bumps the **minor**
856
+ (`0.2.0` to `0.3.0`); a backward-compatible change or fix bumps the
857
+ **patch** (`0.2.0` to `0.2.1`). This is the standard `0.x` convention and
858
+ is what buys us room to break things on feedback without a major bump.
859
+ - Hold **`1.0.0`** until the public API has survived pilot feedback and we're
860
+ ready to commit to "breaking changes bump the major." Jumping to `1.0.0`
861
+ is a stability promise, not a milestone. Don't spend it early.
849
862
 
850
863
  ### Cut a release
851
864
 
852
- 1. **Bump the version** in `packages/neuron-sdk/package.json`
853
- (`version`). Follow semver:
854
- - Pilot phase, we are in **`0.x`**. Because the protocol is still absorbing
855
- pilot-developer feedback, a **breaking** change bumps the **minor**
856
- (`0.2.0` → `0.3.0`); a backward-compatible change or fix bumps the
857
- **patch** (`0.2.0` → `0.2.1`). This is the standard `0.x` convention and
858
- is what buys us room to break things on feedback without a major bump.
859
- - Hold **`1.0.0`** until the public API has survived pilot feedback and we're
860
- ready to commit to "breaking changes bump the major." Jumping to `1.0.0`
861
- is a stability promise, not a milestone — don't spend it early.
862
- 2. **Commit** the bump (reference the Linear ticket) and merge to `main`.
863
- 3. **Tag and push** from `main`:
864
- ```sh
865
- git tag neuron-sdk-v0.2.0 # must equal the package.json version
866
- git push origin neuron-sdk-v0.2.0
867
- ```
868
- The tag is prefixed `neuron-sdk-v` (the Python SDK uses
869
- `neuron-sdk-python-v`) so the two never collide.
870
- 4. **`publish-neuron-sdk.yml`** fires on the tag: it asserts the tag version
871
- matches `package.json`, runs the `prepublish` gate (`build` + `test`), and
872
- publishes to npm. The `npm` GitHub Environment gates the publish behind a
873
- **required-reviewer approval** — approve the run under Actions, and on success
874
- the new version is live at
865
+ 1. Open **Actions, Release, Run workflow** on branch `main`.
866
+ 2. Fill **neuron-sdk** with the new version (`X.Y.Z`). Leave the other lines
867
+ (base, embedded, synapse) empty unless they are being released too; an
868
+ empty line is not touched. Tick **plan only** to read the plan without
869
+ cutting anything.
870
+ 3. The plan job checks the version (shaped `X.Y.Z`, not older than
871
+ `packages/neuron-sdk/package.json`, tag `neuron-sdk-vX.Y.Z` not taken) and
872
+ writes the plan to the run summary.
873
+ 4. A reviewer of the `release` environment approves the run. That is the only
874
+ approval.
875
+ 5. The cut writes the version into `packages/neuron-sdk/package.json`,
876
+ commits it to `main`, tags `neuron-sdk-vX.Y.Z` and pushes both. If the file
877
+ already carries the version (a bump merged by pull request), it only tags.
878
+ 6. From the commit the cut pushed, the run builds and tests the SDK (the
879
+ `prepublish` turbo task), packs it, and publishes the tarball to npm from a
880
+ GitHub-hosted runner. On success the new version is live at
875
881
  [`@holokai/neuron-sdk`](https://www.npmjs.com/package/@holokai/neuron-sdk).
876
882
 
877
- > No `--provenance`: bigbrain is a private repo, so npm's provenance
878
- > attestation (a public transparency-log link back to the source) adds little
879
- > and has historically errored from private repos. Trusted-publishing auth is
880
- > unaffected. Add the flag back if bigbrain ever goes public.
883
+ The tag is prefixed `neuron-sdk-v` (the Python SDK uses `neuron-sdk-python-v`)
884
+ so the two never collide. The workflow file's header comments describe every
885
+ job and how to recover a failed run.
881
886
 
882
- The tag→`package.json` assertion means a mismatched tag fails **before** the
883
- registry is touched — npm refuses to re-upload an existing version, so there's
884
- no half-published state to clean up.
887
+ > No `--provenance`: bigbrain is a private repo, so npm's provenance
888
+ > attestation (a public transparency-log link back to the source) adds little
889
+ > and has historically errored from private repos. Trusted-publishing auth is
890
+ > unaffected. Add the flag back if bigbrain ever goes public.
885
891
 
886
892
  ### Publish auth: npm Trusted Publishing (one-time setup)
887
893
 
888
- CI authenticates to npm via **OIDC trusted publishing** — GitHub Actions
894
+ CI authenticates to npm via **OIDC trusted publishing**: GitHub Actions
889
895
  exchanges a short-lived OIDC token for publish rights, so no long-lived
890
- `NPM_TOKEN` exists to leak. This mirrors the Python SDK's PyPI trusted
891
- publishing. It requires two one-time setups:
896
+ `NPM_TOKEN` exists to leak. It requires two one-time setups:
892
897
 
893
898
  1. **On npmjs.com** (a package admin, once): open the
894
899
  [`@holokai/neuron-sdk`](https://www.npmjs.com/package/@holokai/neuron-sdk)
895
- package → **Settings → Trusted Publisher → GitHub Actions**, and register:
900
+ package, **Settings, Trusted Publisher, GitHub Actions**, and register:
896
901
  - Organization / repository: `holok-ai/bigbrain`
897
- - Workflow filename: `publish-neuron-sdk.yml`
902
+ - Workflow filename: `release.yml`
898
903
  - Environment: `npm`
899
- 2. **In this repo** (once): create the **`npm`** GitHub Environment
900
- (Settings → Environments). Add reviewers/branch protection here if you want a
901
- manual approval gate before each publish.
904
+ 2. **In this repo** (once): the **`npm`** GitHub Environment (Settings,
905
+ Environments) with no required reviewer, because the Release run's one
906
+ approval is on its cut, and a deployment branch rule of `main` only, so a
907
+ copy of `release.yml` on another branch cannot publish to npm.
902
908
 
903
909
  Until the trusted publisher is registered, the `npm publish` step fails with an
904
- auth error — that's the expected "not set up yet" signal, not a code bug.
910
+ auth error. That is the expected "not set up yet" signal, not a code bug.
905
911
 
906
912
  ### Manual publish (fallback / first bootstrap)
907
913
 
908
- The tag flow is the norm. If you ever need to publish by hand (e.g. to bootstrap
914
+ The Release workflow is the norm. If you ever need to publish by hand (e.g. to bootstrap
909
915
  the very first version before trusted publishing is configured):
910
916
 
911
917
  ```sh
@@ -915,6 +921,6 @@ pnpm run publish:npm # runs the prepublishOnly build+test gate, then publi
915
921
  ```
916
922
 
917
923
  This uses your own `npm login` credentials and skips the CI approval gate —
918
- prefer the tag flow for anything a developer will consume.
924
+ prefer the Release workflow for anything a developer will consume.
919
925
 
920
926
  <!-- ci: force full build on the self-hosted runner (HOL-2028 verify); safe to remove -->
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { J as JsonSchema, C as Capability, a as CapabilityHandleOptions, S as ServerToNeuronFrame, N as NeuronLogger, T as Transport, A as AuthCallback, H as Handler, b as CapabilityRegistration, c as TransportCallbacks, d as ClientFrame, P as PostPath } from './transport-Cs5tY2IO.mjs';
2
- export { e as HandlerContext, f as HeartbeatInFlightEntry, L as LeasePayload, g as TaskPayload } from './transport-Cs5tY2IO.mjs';
1
+ import { J as JsonSchema, C as Capability, a as CapabilityHandleOptions, S as ServerToNeuronFrame, N as NeuronLogger, T as Transport, A as AuthCallback, H as Handler, b as CapabilityRegistration, c as TransportCallbacks, d as ClientFrame, P as PostPath } from './transport-Ckcwj-Do.mjs';
2
+ export { e as HandlerContext, f as HeartbeatInFlightEntry, L as LeasePayload, g as TaskPayload } from './transport-Ckcwj-Do.mjs';
3
3
  import { EventEmitter } from 'eventemitter3';
4
4
  import { z } from 'zod';
5
5
  import { ErrorObject } from 'ajv';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { J as JsonSchema, C as Capability, a as CapabilityHandleOptions, S as ServerToNeuronFrame, N as NeuronLogger, T as Transport, A as AuthCallback, H as Handler, b as CapabilityRegistration, c as TransportCallbacks, d as ClientFrame, P as PostPath } from './transport-Cs5tY2IO.js';
2
- export { e as HandlerContext, f as HeartbeatInFlightEntry, L as LeasePayload, g as TaskPayload } from './transport-Cs5tY2IO.js';
1
+ import { J as JsonSchema, C as Capability, a as CapabilityHandleOptions, S as ServerToNeuronFrame, N as NeuronLogger, T as Transport, A as AuthCallback, H as Handler, b as CapabilityRegistration, c as TransportCallbacks, d as ClientFrame, P as PostPath } from './transport-Ckcwj-Do.js';
2
+ export { e as HandlerContext, f as HeartbeatInFlightEntry, L as LeasePayload, g as TaskPayload } from './transport-Ckcwj-Do.js';
3
3
  import { EventEmitter } from 'eventemitter3';
4
4
  import { z } from 'zod';
5
5
  import { ErrorObject } from 'ajv';
package/dist/index.js CHANGED
@@ -558,6 +558,7 @@ var TerminalFailure = class extends Error {
558
558
  this.name = "TerminalFailure";
559
559
  }
560
560
  };
561
+ var DEFAULT_PROGRESS_TIMEOUT_MS = 5e3;
561
562
  function validateInput(zod, validator, jsonSchema, data) {
562
563
  if (zod) {
563
564
  const result = zod.safeParse(data);
@@ -580,7 +581,8 @@ async function runLease(opts) {
580
581
  transport,
581
582
  validator,
582
583
  logger,
583
- signal
584
+ signal,
585
+ progressTimeoutMs = DEFAULT_PROGRESS_TIMEOUT_MS
584
586
  } = opts;
585
587
  const inputCheck = validateInput(zodInput, validator, inputSchema, lease.task.input);
586
588
  if (!inputCheck.ok) {
@@ -595,13 +597,13 @@ async function runLease(opts) {
595
597
  return;
596
598
  }
597
599
  const parsedInput = inputCheck.value;
598
- const progressFn = makeProgressFn(transport, lease.leaseId, logger);
600
+ const reporter = makeProgressReporter(transport, lease.leaseId, logger, signal, progressTimeoutMs);
599
601
  const ctx = {
600
602
  task: lease.task,
601
603
  leaseId: lease.leaseId,
602
604
  signal,
603
605
  log: scopedLogger(logger, lease),
604
- progress: progressFn,
606
+ progress: reporter.progress,
605
607
  fail: (reason, opts2) => {
606
608
  throw new TerminalFailure(reason, opts2?.code);
607
609
  }
@@ -610,7 +612,9 @@ async function runLease(opts) {
610
612
  try {
611
613
  output = await handler(parsedInput, ctx);
612
614
  } catch (err) {
613
- if (signal.aborted || err.name === "AbortError") {
615
+ const cancelled = signal.aborted || err.name === "AbortError";
616
+ if (!cancelled) await reporter.drained();
617
+ if (cancelled) {
614
618
  await sendNack(
615
619
  transport,
616
620
  lease.leaseId,
@@ -644,6 +648,7 @@ async function runLease(opts) {
644
648
  );
645
649
  return;
646
650
  }
651
+ await reporter.drained();
647
652
  const outputCheck = validateInput(zodOutput, validator, outputSchema, output);
648
653
  if (!outputCheck.ok) {
649
654
  await sendNack(
@@ -685,20 +690,37 @@ async function sendNack(transport, leaseId, kind, reason, extras = {}, logger) {
685
690
  );
686
691
  });
687
692
  }
688
- function makeProgressFn(transport, leaseId, logger) {
689
- return (update) => {
690
- void transport.post(POST_PATH.progress, {
691
- type: "progress",
692
- leaseId,
693
- percent: update.percent,
694
- message: update.message,
695
- data: update.data,
696
- sentAt: (/* @__PURE__ */ new Date()).toISOString()
697
- }).catch((err) => {
698
- logger.warn({ err, leaseId }, "progress POST failed; continuing");
699
- });
693
+ function makeProgressReporter(transport, leaseId, logger, signal, timeoutMs) {
694
+ let tail = Promise.resolve();
695
+ return {
696
+ progress: (update) => {
697
+ const frame = {
698
+ type: "progress",
699
+ leaseId,
700
+ percent: update.percent,
701
+ message: update.message,
702
+ data: update.data,
703
+ sentAt: (/* @__PURE__ */ new Date()).toISOString()
704
+ };
705
+ tail = tail.then(async () => {
706
+ if (signal.aborted) return;
707
+ try {
708
+ await withTimeout(transport.post(POST_PATH.progress, frame), timeoutMs);
709
+ } catch (err) {
710
+ logger.warn({ err, leaseId }, "progress POST failed; continuing");
711
+ }
712
+ });
713
+ },
714
+ drained: () => tail
700
715
  };
701
716
  }
717
+ function withTimeout(work, ms) {
718
+ let timer;
719
+ const timeout = new Promise((_resolve, reject) => {
720
+ timer = setTimeout(() => reject(new Error(`timed out after ${ms}ms`)), ms);
721
+ });
722
+ return Promise.race([work, timeout]).finally(() => clearTimeout(timer));
723
+ }
702
724
  function scopedLogger(logger, lease) {
703
725
  const scope = { leaseId: lease.leaseId, taskId: lease.task.taskId };
704
726
  return {