novoagents 0.5.0-alpha.21 → 0.5.0-alpha.22

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/CHANGELOG.md CHANGED
@@ -3,6 +3,34 @@
3
3
  Customer-facing release notes for the supported pre-launch `novoagents` SDK
4
4
  and hosted API contract. Current documentation is the source of truth.
5
5
 
6
+ ## 0.5.0-alpha.22 - 2026-08-18
7
+
8
+ - **Every executed tool call announces its start and its exact end.** An
9
+ ordinary tool wait is now a complete `data-novo-heartbeat` series: an
10
+ `executing_tool` opener (`beat: 0`, `elapsedMs: 0`) written the instant the
11
+ tool body starts, a beat per interval while it runs, and `settled` with the
12
+ call's exact `elapsedMs` when it ends. Sub-interval calls used to emit no
13
+ frame at all, and the first beat arrived one interval in — so a sibling
14
+ queued behind an exclusive tool was indistinguishable from a running one,
15
+ because `tool-input-*` is written when the model ASKS, before the scheduler
16
+ admits the call. Time a call from its own series (`emittedAt - elapsedMs`),
17
+ never from when you saw its `tool-input-start`, and stop on `settled`.
18
+ Delegation calls keep their existing lifecycle (`data-novo-task` is their
19
+ start signal). Additive; `beat` is `0` only on this opener.
20
+ - **The browser message reducer keeps one heartbeat part per wait.**
21
+ `data-novo-heartbeat` was classified as run-level control state and dropped
22
+ from `message.parts`, which contradicted its own contract ("a heartbeat `id`
23
+ is stable within one wait and replaces its prior value") and left the
24
+ run-level `heartbeat` (latest wins) unable to tell two concurrent waits apart.
25
+ Each wait's frames now share one replace-in-place part, so a parked
26
+ transcript holds one part per tool call or delegated wave. The run-level
27
+ `heartbeat` is unchanged.
28
+ - The stream reader mints no progress activity line for a wait's opener or its
29
+ `settled` frame; a closed tool wait no longer reads as "Waiting for
30
+ delegated work."
31
+ - Docs now name `settled` explicitly (it shipped in the stream schema 12 line
32
+ without a changelog entry). No wire-contract or stream-schema revision.
33
+
6
34
  ## 0.5.0-alpha.21 - 2026-08-17
7
35
 
8
36
  - **`authorization_incomplete` leaves `NovoErrorCode`.** No route ever produced
@@ -10,12 +10,17 @@ const MESSAGE_CONTROL_DATA_TYPES = new Set([
10
10
  'data-novo-usage',
11
11
  'data-novo-step-terminal',
12
12
  'data-novo-model-step-restart',
13
- // Heartbeats are run-level liveness state (latest wins); accumulating one
14
- // message part per beat grows a parked transcript without bound.
15
- 'data-novo-heartbeat',
16
13
  ]);
17
14
  const KNOWN_DATA_TYPES = new Set([
18
15
  ...MESSAGE_CONTROL_DATA_TYPES,
16
+ // One heartbeat part per WAIT, replaced in place: the producer keeps the
17
+ // part id stable for a wait's whole series (`heartbeat_<waitId>`), so a
18
+ // parked transcript holds one part per tool call or delegated wave, never
19
+ // one per beat. Retaining it is what lets a consumer time each parallel tool
20
+ // call from its own series (`emittedAt - elapsedMs`) and freeze it on
21
+ // `settled`; the run-level `heartbeat` (latest wins) is liveness only and
22
+ // cannot tell two concurrent waits apart.
23
+ 'data-novo-heartbeat',
19
24
  'data-novo-terminal',
20
25
  'data-novo-task',
21
26
  'data-novo-followup-replayed',
@@ -387,6 +392,22 @@ export function createNovoMessageSession(options = {}) {
387
392
  // The output may still be buffered with no visible part index.
388
393
  tools.delete(toolCallId);
389
394
  }
395
+ // A superseded call's heartbeat wait goes with it: the part is keyed by
396
+ // wait id, not tool call id, so find it by the `toolCallId` it carries.
397
+ // Left behind it would be execution evidence for a call the transcript no
398
+ // longer holds, accumulating one orphan per restarted attempt.
399
+ if (toolCallIds.size > 0) {
400
+ message.parts.forEach((part, index) => {
401
+ if (part.type !== 'data-novo-heartbeat')
402
+ return;
403
+ const data = part.data;
404
+ if (isRecord(data) &&
405
+ typeof data.toolCallId === 'string' &&
406
+ toolCallIds.has(data.toolCallId)) {
407
+ removedIndexes.add(index);
408
+ }
409
+ });
410
+ }
390
411
  for (const sourceId of sourceIds) {
391
412
  const index = latestSourceParts.get(sourceId);
392
413
  if (index !== undefined)
package/dist/types.d.ts CHANGED
@@ -442,19 +442,31 @@ export type NovoTaskChunk = {
442
442
  *
443
443
  * - `phase: 'delegating'` is an identity-redacted frame emitted as an internal
444
444
  * delegation call begins, before a worker task card can exist.
445
- * - `phase: 'executing_tool'` repeats roughly every 15s until the call settles.
445
+ * - `phase: 'executing_tool'` opens an ordinary tool wait the instant the tool
446
+ * body starts (`beat: 0`, `elapsedMs: 0`) and repeats roughly every 15s
447
+ * until the call settles. The opener is your only evidence that a call is
448
+ * EXECUTING: `tool-input-*` is written when the model asks, before the
449
+ * scheduler admits the call, so a sibling queued behind an exclusive tool
450
+ * looks identical to a running one until this frame arrives. Time the call
451
+ * from its own series (`emittedAt - elapsedMs`), never from when you saw its
452
+ * `tool-input-start`.
446
453
  * - `phase: 'awaiting_subagents'` is a SINGLE frame that opens a delegated wait
447
454
  * BEFORE any launch begins, carrying the intended task count with empty
448
455
  * `workerRefs` — per-worker refs arrive on each `data-novo-task` as its
449
456
  * launch succeeds. An orchestrator suspended on its subagents runs no code of
450
457
  * its own, so track each task with `data-novo-task` and its worker sub-stream
451
458
  * rather than expecting a series.
452
- * - `phase: 'settled'` closes an `'executing_tool'` wait, and is the frame a
453
- * live timer must stop on. It closes ONLY that wait — a delegated wait is a
454
- * single frame and is never closed by a heartbeat, so do not block a spinner
455
- * on one arriving.
459
+ * - `phase: 'settled'` closes an `'executing_tool'` wait — every executed
460
+ * ordinary tool call, sub-interval ones included — carrying the call's exact
461
+ * `elapsedMs` plus its `toolCallId`. It is the frame a live timer must stop
462
+ * on. An internal delegation call that beat past the interval also closes
463
+ * with an identity-free `settled` on its opaque wait id. The single
464
+ * `'awaiting_subagents'` wave frame is never closed by a heartbeat, so do
465
+ * not block a spinner on one arriving.
456
466
  *
457
- * Purely informational.
467
+ * The message reducer keeps ONE heartbeat part per wait (`id` is stable for
468
+ * the series and each frame replaces the last), so a parked transcript holds
469
+ * one part per tool call or delegated wave. Purely informational.
458
470
  */
459
471
  export type NovoHeartbeatChunk = {
460
472
  type: 'data-novo-heartbeat';
@@ -475,19 +487,32 @@ export type NovoHeartbeatChunk = {
475
487
  * beat instead counts up forever, which is how an 18-second tool call
476
488
  * renders as "Running command - 5m 19s".
477
489
  *
478
- * Three cases where it never arrives, all expected:
479
- * - a tool call that ends inside the first beat interval opens no wait, so
480
- * it closes none — there are no frames for it at all;
490
+ * Two cases where it never arrives, both expected:
481
491
  * - an `'awaiting_subagents'` wait is a single frame and is NEVER closed by
482
492
  * a heartbeat, because a suspended orchestrator runs no code of its own.
483
493
  * Track delegated work with `data-novo-task` and the worker sub-streams;
484
- * - a `'delegating'` frame is likewise never closed — it is superseded by
485
- * the wave's `'awaiting_subagents'` frame and the `data-novo-task` cards.
494
+ * - the reader-synthesized `'delegating'` frame is never closed — it is
495
+ * superseded by the wave's `'awaiting_subagents'` frame and the
496
+ * `data-novo-task` cards. (A LIVE delegation beat loop, on a call that
497
+ * outlived the interval, does close its opaque wait with an identity-free
498
+ * `'settled'`; nothing about a delegated task's outcome rides on it.)
499
+ *
500
+ * Every executed ordinary tool call opens (`beat: 0`) and settles, so a
501
+ * `tool-input-available` with no opener means the call has not started
502
+ * executing — queued, awaiting approval, or not yet admitted.
486
503
  */
487
504
  phase: 'delegating' | 'awaiting_subagents' | 'executing_tool' | 'settled';
488
- /** 1-based monotonic beat within the current wait. */
505
+ /**
506
+ * Monotonic beat within the current wait: `0` on the frame that opens an
507
+ * ordinary tool wait, then 1-based per interval. The `awaiting_subagents`
508
+ * opener and the reader-synthesized `delegating` frame use `1`.
509
+ */
489
510
  beat: number;
490
- /** Elapsed wall clock since the wait began; 0 on the frame that opens one. */
511
+ /**
512
+ * Elapsed wall clock since the wait began, on the producer's clock; 0 on
513
+ * the frame that opens one and the call's exact duration on `'settled'`.
514
+ * `emittedAt - elapsedMs` is the wait's start from any frame of the series.
515
+ */
491
516
  elapsedMs: number;
492
517
  /** Outstanding items this heartbeat is still waiting on. */
493
518
  activeTaskCount: number;
@@ -515,9 +540,14 @@ export type NovoHeartbeatChunk = {
515
540
  * `data-novo-task` and the worker sub-streams instead.
516
541
  */
517
542
  emittedAt?: string;
518
- /** The executing call, matching its `tool-input-*` frames (`executing_tool`). */
543
+ /**
544
+ * The call this wait belongs to, matching its `tool-input-*` frames. On
545
+ * every frame of an ordinary tool wait — opener, beats, and `settled` —
546
+ * so a consumer can correlate the closer even when it replaced the opener
547
+ * in place. Absent on delegation and wave frames.
548
+ */
519
549
  toolCallId?: string;
520
- /** Public tool name of the executing call (`executing_tool`). */
550
+ /** Public tool name of the call, on the same frames as `toolCallId`. */
521
551
  toolName?: string;
522
552
  };
523
553
  };
package/docs/changelog.md CHANGED
@@ -3,6 +3,29 @@
3
3
  Release notes describe the supported pre-launch API and `novoagents` SDK.
4
4
  Current API pages remain the source of truth for new integrations.
5
5
 
6
+ ## 0.5.0-alpha.22 - 2026-08-18
7
+
8
+ - **Every executed tool call announces its start and its exact end.** An
9
+ ordinary tool wait is a complete `data-novo-heartbeat` series: an
10
+ `executing_tool` opener (`beat: 0`, `elapsedMs: 0`) the instant the tool body
11
+ starts, a beat per interval, and `settled` with the exact `elapsedMs`. The
12
+ opener is the only evidence a call is executing rather than queued
13
+ (`tool-input-*` is written when the model asks, before admission). Time a call
14
+ from its own series as `emittedAt - elapsedMs`; stop on `settled`.
15
+ - The browser message reducer keeps one `data-novo-heartbeat` part per wait,
16
+ replaced in place, so concurrent tool calls can be timed independently from
17
+ `run.messages`. The run-level `heartbeat` is unchanged.
18
+ - No wire-contract or stream-schema revision.
19
+
20
+ ## 0.5.0-alpha.21 - 2026-08-17
21
+
22
+ - `authorization_incomplete` leaves `NovoErrorCode`; it is a `github_error`
23
+ value on the return to `returnUrl`. Wire contract revised; no stream-schema
24
+ change.
25
+ - An organization owner's approval no longer binds an installation on its own:
26
+ once the owner approves, call `authorize({ connectionId })` again and complete
27
+ the selection as for a new connect.
28
+
6
29
  ## 0.5.0-alpha.20 - 2026-08-17
7
30
 
8
31
  - **Finish a GitHub connect in your own product.** The install callback binds
@@ -68,7 +68,7 @@ The stream is composed of a fixed sequence of chunk families. Per assistant step
68
68
  - `data-novo-tool-result-overflow` interleaved when a tool result spills past the budget. The clipping applies to the result's readable text; media blocks (image, audio, video) are dropped whole rather than binary-truncated, since a sliced base64 payload decodes to a corrupt file
69
69
  - `data-novo-browser-session` interleaved when the managed [browser](https://novoagents.ai/docs/concepts/managed-browser.md) opens, changes page, or closes, always before the turn's `data-novo-step-terminal`
70
70
  - `data-novo-task` interleaved when an orchestrator starts or finishes a delegated [subagent](https://novoagents.ai/docs/concepts/subagents.md) task
71
- - `data-novo-heartbeat` interleaved during long tool calls and delegated waits. See the [taxonomy](#the-data-novo-taxonomy) for timer and parallel-call handling
71
+ - `data-novo-heartbeat` opening, beating through, and closing each executed tool call, and during delegated waits. See the [taxonomy](#the-data-novo-taxonomy) for timer and parallel-call handling
72
72
  4. **`data-novo-step-terminal`**: closes an assistant **turn**, a maximal run of model steps that isn't interrupted by a pause. The model takes as many `start-step`…`finish-step` model steps as the task needs inside one turn, and a single `data-novo-step-terminal` closes the turn once the model stops or the run pauses. A run that never pauses is one turn, so you get **exactly one** `data-novo-step-terminal`, just before the run's `data-novo-usage`/`data-novo-terminal`; a run that pauses (approval, question, worker) and resumes emits one per resumed segment. It carries the turn's messages, the turn's **cumulative usage delta** (summed across the turn's model steps), a `modelFinishReason`, a generic provider-neutral `modelLabel`, the run's `chargeId` (the join key to the `usage.events` debit row), and optional `perf` timing.
73
73
  5. **Pause chunks (between steps, when applicable):**
74
74
  - `data-novo-input-requested` → `data-novo-input-resolved` around any pause. The `request.kind` (`'approval_request'` or `'question'`) tells the two apart; an approval is just an input request whose options are `[approve, deny]`.
@@ -146,7 +146,7 @@ A `status: 'failed'` with `terminalReason: null` means the model errored. A `sta
146
146
  | `data-novo-tool-server-unavailable` | An MCP tool server is unavailable to the run: disabled by policy (`errorCode: 'policy_denied'`), or its tool listing failed. A listing failure is retried and then fails the step; the run never continues without the server's tools. |
147
147
  | `data-novo-http-connection-unavailable` | An HTTP connection is unavailable to the run: disabled by policy (`errorCode: 'policy_denied'`), or its OpenAPI spec failed to load or parse (`errorCode: 'spec_unreachable' \| 'spec_invalid' \| 'base_url_missing'`), in which case the failure is retried and then fails the step; the run never continues without the connection's operations. Operation-level non-2xx responses are not this chunk; they return to the model as tool output. |
148
148
  | `data-novo-task` | A delegated [subagent](https://novoagents.ai/docs/concepts/subagents.md) task lifecycle on the orchestrator stream. One id-reconciled card per task carrying `{ rootRunId, workerRef, role: 'worker' \| 'advisor', status: 'started' \| 'completed' \| 'failed' \| 'cancelled', label?, summary? }`. Render `label` as the UI title; open the sub-stream with `runs.workers.stream(rootRunId, workerRef)`. |
149
- | `data-novo-heartbeat` | Optional liveness while a tool or delegated task is still pending. Carries `{ runId, rootRunId, threadId, agentId, phase, beat, elapsedMs, activeTaskCount, workerRefs }`, plus `toolCallId` and `toolName` for `executing_tool`, plus `emittedAt` on frames a live beat loop wrote. Measure staleness against `emittedAt` (ISO 8601, server time) rather than arrival time: reconnecting replays history, so an old beat can arrive seconds ago. Two opener shapes omit it and prove nothing about current liveness: the reader-synthesized `delegating` frame and the single `awaiting_subagents` frame. A live delegation tool beat also has phase `delegating` and does carry `emittedAt`, so use field presence rather than `phase` as provenance. Tool waits can emit repeated `executing_tool` beats followed by `settled`; stop any timer on `settled`. Delegated waits emit one `awaiting_subagents` frame, so track completion through `data-novo-task` and worker sub-streams. Parallel tool calls have independent series. A heartbeat `id` is stable within one wait and replaces its prior value. This frame is safe to ignore. |
149
+ | `data-novo-heartbeat` | Optional liveness while a tool or delegated task is still pending. Carries `{ runId, rootRunId, threadId, agentId, phase, beat, elapsedMs, activeTaskCount, workerRefs }`, plus `toolCallId` and `toolName` on every frame of an ordinary tool wait (opener, beats, and `settled`), plus `emittedAt` on frames a live beat loop wrote. Measure staleness against `emittedAt` (ISO 8601, server time) rather than arrival time: reconnecting replays history, so an old beat can arrive seconds ago. Two opener shapes omit it and prove nothing about current liveness: the reader-synthesized `delegating` frame and the single `awaiting_subagents` frame. A live delegation tool beat also has phase `delegating` and does carry `emittedAt`, so use field presence rather than `phase` as provenance. Every executed ordinary tool call is a complete series: an `executing_tool` opener (`beat: 0`, `elapsedMs: 0`) the instant the tool body starts, a beat per interval while it runs, and `settled` with the exact `elapsedMs` when it ends. The opener is the only evidence a call is executing rather than queued (`tool-input-*` is written when the model asks, before the scheduler admits the call); time a call from its own series as `emittedAt - elapsedMs`, and stop any timer on `settled`. Delegated waves emit one `awaiting_subagents` frame that is never closed (an internal delegation call that outlived the interval closes its own opaque wait with an identity-free `settled`), so track completion through `data-novo-task` and worker sub-streams. Parallel tool calls have independent series. A heartbeat `id` is stable within one wait and replaces its prior value. This frame is safe to ignore. |
150
150
  | `data-novo-worker-meta` | First frame of a worker sub-stream. Carries root run identity, `workerRef`, `progressEnabled`, status, role, label, and the full dispatch prompt. |
151
151
  | `data-novo-worker-terminal` | Last frame of a worker sub-stream. Carries `workerRef`, terminal status/reason, and an optional structured error. |
152
152
 
package/docs/llms-full.md CHANGED
@@ -941,7 +941,7 @@ The stream is composed of a fixed sequence of chunk families. Per assistant step
941
941
  - `data-novo-tool-result-overflow` interleaved when a tool result spills past the budget. The clipping applies to the result's readable text; media blocks (image, audio, video) are dropped whole rather than binary-truncated, since a sliced base64 payload decodes to a corrupt file
942
942
  - `data-novo-browser-session` interleaved when the managed [browser](https://novoagents.ai/docs/concepts/managed-browser.md) opens, changes page, or closes, always before the turn's `data-novo-step-terminal`
943
943
  - `data-novo-task` interleaved when an orchestrator starts or finishes a delegated [subagent](https://novoagents.ai/docs/concepts/subagents.md) task
944
- - `data-novo-heartbeat` interleaved during long tool calls and delegated waits. See the [taxonomy](#the-data-novo-taxonomy) for timer and parallel-call handling
944
+ - `data-novo-heartbeat` opening, beating through, and closing each executed tool call, and during delegated waits. See the [taxonomy](#the-data-novo-taxonomy) for timer and parallel-call handling
945
945
  4. **`data-novo-step-terminal`**: closes an assistant **turn**, a maximal run of model steps that isn't interrupted by a pause. The model takes as many `start-step`…`finish-step` model steps as the task needs inside one turn, and a single `data-novo-step-terminal` closes the turn once the model stops or the run pauses. A run that never pauses is one turn, so you get **exactly one** `data-novo-step-terminal`, just before the run's `data-novo-usage`/`data-novo-terminal`; a run that pauses (approval, question, worker) and resumes emits one per resumed segment. It carries the turn's messages, the turn's **cumulative usage delta** (summed across the turn's model steps), a `modelFinishReason`, a generic provider-neutral `modelLabel`, the run's `chargeId` (the join key to the `usage.events` debit row), and optional `perf` timing.
946
946
  5. **Pause chunks (between steps, when applicable):**
947
947
  - `data-novo-input-requested` → `data-novo-input-resolved` around any pause. The `request.kind` (`'approval_request'` or `'question'`) tells the two apart; an approval is just an input request whose options are `[approve, deny]`.
@@ -1019,7 +1019,7 @@ A `status: 'failed'` with `terminalReason: null` means the model errored. A `sta
1019
1019
  | `data-novo-tool-server-unavailable` | An MCP tool server is unavailable to the run: disabled by policy (`errorCode: 'policy_denied'`), or its tool listing failed. A listing failure is retried and then fails the step; the run never continues without the server's tools. |
1020
1020
  | `data-novo-http-connection-unavailable` | An HTTP connection is unavailable to the run: disabled by policy (`errorCode: 'policy_denied'`), or its OpenAPI spec failed to load or parse (`errorCode: 'spec_unreachable' \| 'spec_invalid' \| 'base_url_missing'`), in which case the failure is retried and then fails the step; the run never continues without the connection's operations. Operation-level non-2xx responses are not this chunk; they return to the model as tool output. |
1021
1021
  | `data-novo-task` | A delegated [subagent](https://novoagents.ai/docs/concepts/subagents.md) task lifecycle on the orchestrator stream. One id-reconciled card per task carrying `{ rootRunId, workerRef, role: 'worker' \| 'advisor', status: 'started' \| 'completed' \| 'failed' \| 'cancelled', label?, summary? }`. Render `label` as the UI title; open the sub-stream with `runs.workers.stream(rootRunId, workerRef)`. |
1022
- | `data-novo-heartbeat` | Optional liveness while a tool or delegated task is still pending. Carries `{ runId, rootRunId, threadId, agentId, phase, beat, elapsedMs, activeTaskCount, workerRefs }`, plus `toolCallId` and `toolName` for `executing_tool`, plus `emittedAt` on frames a live beat loop wrote. Measure staleness against `emittedAt` (ISO 8601, server time) rather than arrival time: reconnecting replays history, so an old beat can arrive seconds ago. Two opener shapes omit it and prove nothing about current liveness: the reader-synthesized `delegating` frame and the single `awaiting_subagents` frame. A live delegation tool beat also has phase `delegating` and does carry `emittedAt`, so use field presence rather than `phase` as provenance. Tool waits can emit repeated `executing_tool` beats followed by `settled`; stop any timer on `settled`. Delegated waits emit one `awaiting_subagents` frame, so track completion through `data-novo-task` and worker sub-streams. Parallel tool calls have independent series. A heartbeat `id` is stable within one wait and replaces its prior value. This frame is safe to ignore. |
1022
+ | `data-novo-heartbeat` | Optional liveness while a tool or delegated task is still pending. Carries `{ runId, rootRunId, threadId, agentId, phase, beat, elapsedMs, activeTaskCount, workerRefs }`, plus `toolCallId` and `toolName` on every frame of an ordinary tool wait (opener, beats, and `settled`), plus `emittedAt` on frames a live beat loop wrote. Measure staleness against `emittedAt` (ISO 8601, server time) rather than arrival time: reconnecting replays history, so an old beat can arrive seconds ago. Two opener shapes omit it and prove nothing about current liveness: the reader-synthesized `delegating` frame and the single `awaiting_subagents` frame. A live delegation tool beat also has phase `delegating` and does carry `emittedAt`, so use field presence rather than `phase` as provenance. Every executed ordinary tool call is a complete series: an `executing_tool` opener (`beat: 0`, `elapsedMs: 0`) the instant the tool body starts, a beat per interval while it runs, and `settled` with the exact `elapsedMs` when it ends. The opener is the only evidence a call is executing rather than queued (`tool-input-*` is written when the model asks, before the scheduler admits the call); time a call from its own series as `emittedAt - elapsedMs`, and stop any timer on `settled`. Delegated waves emit one `awaiting_subagents` frame that is never closed (an internal delegation call that outlived the interval closes its own opaque wait with an identity-free `settled`), so track completion through `data-novo-task` and worker sub-streams. Parallel tool calls have independent series. A heartbeat `id` is stable within one wait and replaces its prior value. This frame is safe to ignore. |
1023
1023
  | `data-novo-worker-meta` | First frame of a worker sub-stream. Carries root run identity, `workerRef`, `progressEnabled`, status, role, label, and the full dispatch prompt. |
1024
1024
  | `data-novo-worker-terminal` | Last frame of a worker sub-stream. Carries `workerRef`, terminal status/reason, and an optional structured error. |
1025
1025
 
@@ -6309,6 +6309,29 @@ source: https://novoagents.ai/docs/changelog.md
6309
6309
  Release notes describe the supported pre-launch API and `novoagents` SDK.
6310
6310
  Current API pages remain the source of truth for new integrations.
6311
6311
 
6312
+ ## 0.5.0-alpha.22 - 2026-08-18
6313
+
6314
+ - **Every executed tool call announces its start and its exact end.** An
6315
+ ordinary tool wait is a complete `data-novo-heartbeat` series: an
6316
+ `executing_tool` opener (`beat: 0`, `elapsedMs: 0`) the instant the tool body
6317
+ starts, a beat per interval, and `settled` with the exact `elapsedMs`. The
6318
+ opener is the only evidence a call is executing rather than queued
6319
+ (`tool-input-*` is written when the model asks, before admission). Time a call
6320
+ from its own series as `emittedAt - elapsedMs`; stop on `settled`.
6321
+ - The browser message reducer keeps one `data-novo-heartbeat` part per wait,
6322
+ replaced in place, so concurrent tool calls can be timed independently from
6323
+ `run.messages`. The run-level `heartbeat` is unchanged.
6324
+ - No wire-contract or stream-schema revision.
6325
+
6326
+ ## 0.5.0-alpha.21 - 2026-08-17
6327
+
6328
+ - `authorization_incomplete` leaves `NovoErrorCode`; it is a `github_error`
6329
+ value on the return to `returnUrl`. Wire contract revised; no stream-schema
6330
+ change.
6331
+ - An organization owner's approval no longer binds an installation on its own:
6332
+ once the owner approves, call `authorize({ connectionId })` again and complete
6333
+ the selection as for a new connect.
6334
+
6312
6335
  ## 0.5.0-alpha.20 - 2026-08-17
6313
6336
 
6314
6337
  - **Finish a GitHub connect in your own product.** The install callback binds
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "novoagents",
3
- "version": "0.5.0-alpha.21",
3
+ "version": "0.5.0-alpha.22",
4
4
  "description": "Thin TypeScript SDK for the Novo Agents hosted API.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",