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 +28 -0
- package/dist/browser/reducer.js +24 -3
- package/dist/types.d.ts +45 -15
- package/docs/changelog.md +23 -0
- package/docs/concepts/streaming.md +2 -2
- package/docs/llms-full.md +25 -2
- package/package.json +1 -1
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
|
package/dist/browser/reducer.js
CHANGED
|
@@ -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'`
|
|
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
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
* on
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
* -
|
|
485
|
-
* the wave's `'awaiting_subagents'` frame and the
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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`
|
|
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`
|
|
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`
|
|
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`
|
|
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
|