@asgard-js/core 0.3.4 → 0.3.5

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.
Files changed (2) hide show
  1. package/README.md +40 -4
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -114,7 +114,7 @@ const client = new AsgardServiceClient({
114
114
 
115
115
  ## API Reference
116
116
 
117
- The core package exports three main classes for different levels of abstraction (`AsgardServiceClient`, `Channel`, `Conversation`), an `HttpError` class with an `isHttpError` type guard for HTTP failure handling, and authentication types for dynamic API key management:
117
+ The core package exports three main classes for different levels of abstraction (`AsgardServiceClient`, `Channel`, `Conversation`), an `HttpError` class with an `isHttpError` type guard for HTTP failure handling, authentication types for dynamic API key management, and a set of framework-agnostic **derived-state** helpers (Task Check List / Subagent List) for headless / non-React consumers — see [Derived State](#derived-state):
118
118
 
119
119
  <a id="asgardserviceclient"></a>
120
120
  <br/>
@@ -144,6 +144,8 @@ The main client class for interacting with the Asgard AI platform.
144
144
  - **fetchSse(payload, options?)**: Send a message via Server-Sent Events. `payload.action` is a `FetchSseAction` value — `NONE` for a normal message, `RESET_CHANNEL` to (re)initialize the channel, `RESPONSE_TOOL_CALL_CONSENT` to answer a consent prompt
145
145
  - **uploadFile(file, customChannelId)**: Upload file to Blob API and return BlobUploadResponse
146
146
  - **downloadChannelHomeFile(relativePath, customChannelId)**: `Promise<ChannelHomeDownloadResult>` - Download a file from the channel's Channel Home file-exchange plane (backs `channel-home://` URI actions); resolves to `{ blob, filename }`
147
+ - **rejoinSse(customChannelId, options?)**: Cold-start transcript rejoin — a `GET /message/sse` with an empty `Last-Event-ID` that replays the channel's collapsed history through the same reducer, so a returning user sees their prior conversation without re-POSTing. Optional on `IAsgardServiceClient` for backward compatibility
148
+ - **channelMetadata(customChannelId)**: `Promise<ChannelMetadata | null>` - Join-init existence + restore gate — `GET /channel/metadata`; resolves to the metadata on `200`, `null` on `404` (channel does not exist), and rejects on any other error. `ChannelMetadata` is `{ title: string | null; runState: 'RUNNING' | 'IDLE'; lastActivityAt?: string }`. Optional on `IAsgardServiceClient` for backward compatibility
147
149
  - **on(event, handler)**: Listen to a specific SSE event. `event` must be an `EventType` value (e.g. `EventType.MESSAGE`), not a plain string; registering a listener for an event replaces any previous one
148
150
  - **detach({ timeoutMs })**: Detach from the owning component without aborting in-flight runs — the connection stays open so the backend can finish the current run, then auto-closes once all runs settle (or after `timeoutMs` as a safety net). Backs the React `keepConnectionOnUnmount` prop
149
151
  - **close()**: Close the SSE connection and clean up resources (idempotent)
@@ -169,12 +171,16 @@ Higher-level abstraction for managing a conversation channel with reactive state
169
171
 
170
172
  #### Static Methods
171
173
 
172
- - **Channel.reset(config, payload?, options?)**: `Promise<Channel>` - Create and initialize a new channel
174
+ - **Channel.reset(config, payload?, options?)**: `Promise<Channel>` - Create a channel and send `RESET_CHANNEL`, starting a fresh conversation (the server replies with a welcome message)
175
+ - **Channel.restore(config, options?)**: `Promise<Channel>` - Join an **existing** channel without resetting it — seeds the title from `config.channelTitle` and replays the server transcript via `rejoinSse`, preserving history / session / title. This is the join-without-wiping path behind the metadata-gated mount (F-015)
176
+ - **Channel.create(config)**: `Channel` - Create a channel and subscribe to its state without any SSE request (no reset, no rejoin); the first connection happens when you call `sendMessage`
173
177
 
174
178
  #### Instance Methods
175
179
 
176
180
  - **sendMessage(payload, options?)**: `Promise<void>` - Send a message through the channel
177
181
  - **replyToolCallConsents(answers, options?, payload?)**: `Promise<void>` - Reply to a pending tool-call consent prompt. `answers` is an array of `ToolCallConsentAnswer` (each `{ toolCallId, result, denyReason }`, where `result` is a `ToolCallConsentResult` value)
182
+ - **getTasks() / getSubagents() / getChannelTitle()**: `Task[]` / `Subagent[]` / `string | null` - Current immutable snapshots of the derived state (for `getSnapshot()`-style bridging; see [Derived State](#derived-state))
183
+ - **setChannelTitle(title)**: `void` - Seed or override the reactive channel title (F-016)
178
184
  - **close()**: `void` - Close the channel and cleanup subscriptions
179
185
 
180
186
  #### Configuration (ChannelConfig)
@@ -183,12 +189,16 @@ Higher-level abstraction for managing a conversation channel with reactive state
183
189
  - **customChannelId**: `string` - Unique channel identifier
184
190
  - **customMessageId?**: `string` - Optional message ID
185
191
  - **conversation**: `Conversation` - Initial conversation state
186
- - **statesObserver?**: `ObserverOrNext<ChannelStates>` - Observer for channel state changes
192
+ - **channelTitle?**: `string | null` - Seed for the reactive channel-title store (F-016), typically the `title` from `channelMetadata()`. `null` = unnamed
193
+ - **statesObserver?**: `ObserverOrNext<ChannelStates>` - Observer for channel state changes. `ChannelStates` carries `isConnecting`, `conversation`, and (since 0.3.x) the derived `tasks: Task[]`, `subagents: Subagent[]`, and `channelTitle: string | null`
187
194
 
188
195
  #### Properties
189
196
 
190
197
  - **customChannelId**: `string` - The channel identifier
191
198
  - **customMessageId?**: `string` - Optional message identifier
199
+ - **tasks$**: `Observable<Task[]>` - Reactive Task Check List store; replays the current snapshot and emits only when the list changes (F-010 / F-013)
200
+ - **subagents$**: `Observable<Subagent[]>` - Reactive Subagent List store; replays the current snapshot and emits only when the list changes (F-012 / F-013)
201
+ - **channelTitle$**: `Observable<string | null>` - Reactive channel-title store; seeded from metadata, updated by `title.update` (F-016)
192
202
 
193
203
  #### Example Usage
194
204
 
@@ -242,7 +252,9 @@ Immutable conversation state manager that handles message updates and SSE event
242
252
 
243
253
  - **ConversationUserMessage**: User-sent messages with `text` and `time`
244
254
  - **ConversationBotMessage**: Bot responses with `message`, `isTyping`, `typingText`, `eventType`
245
- - **ConversationToolCallMessage**: Tool-call entries with `toolName`, `reason`, `parameter`, `result`, `isComplete`
255
+ - **ConversationToolCallMessage**: Tool-call entries with `toolName`, `reason`, `parameter`, `result`, `isComplete`, and (since 0.3.x) `isError` (backend failure flag, F-009), `toolUseId` / `parentToolUseId` (subagent correlation, F-012)
256
+ - **ConversationThinkingMessage**: Extended-thinking (reasoning) block with `text` and `isThinking`, rendered as a collapsible block separate from the answer (F-001)
257
+ - **ConversationSubagentMessage**: Subagent lifecycle entry with `kind` (`start` / `complete`), `parentToolUseId`, `status`, `summary` (F-012)
246
258
  - **ConversationErrorMessage**: Error messages with `error` details
247
259
 
248
260
  #### Example Usage
@@ -397,6 +409,30 @@ interface ChannelHomeDownloadResult {
397
409
  }
398
410
  ```
399
411
 
412
+ <a id="derived-state"></a>
413
+ <br/>
414
+
415
+ ### Derived State (Task Check List / Subagent List)
416
+
417
+ The Task Check List (F-010) and Subagent List (F-012) are pure folds over the conversation, exposed as **framework-agnostic** reactive slices so you can render them outside React — in Vue, Svelte, or vanilla JS. Each slice replays its current immutable snapshot and only emits when that slice actually changes (unrelated high-frequency message deltas are suppressed).
418
+
419
+ The simplest path is the reactive stores already on `Channel` (`channel.tasks$`, `channel.subagents$`, `channel.channelTitle$`) plus the snapshot getters (`getTasks()`, `getSubagents()`, `getChannelTitle()`). To build the slices from a bare `conversation$` yourself, use `createDerivedStores(conversation$)`:
420
+
421
+ ```typescript
422
+ import { createDerivedStores } from '@asgard-js/core';
423
+
424
+ const stores = createDerivedStores(conversation$);
425
+ // stores: { tasks$, subagents$, getTasks(), getSubagents(), teardown() }
426
+ const sub = stores.tasks$.subscribe(tasks => renderTaskList(tasks));
427
+ // ... later
428
+ sub.unsubscribe();
429
+ stores.teardown();
430
+ ```
431
+
432
+ For one-shot derivation without subscriptions, `deriveTasks(conversation)` and `deriveSubagents(conversation)` return the current lists directly. Lower-level building blocks are also exported: the reducers `reduceTaskEvents` / `reduceSubagents`, the type guards `isTaskTool` / `isAgentTool` / `isSubagentChildTool`, the adapter `conversationToSubagentEvents`, the structural-equality helpers `tasksEqual` / `subagentsEqual`, and the types `Task`, `Subagent`, `DerivedStores`, `TaskToolEvent`, `SubagentEvent`.
433
+
434
+ > In React, prefer the `useTaskList(channel)`, `useSubagents(channel)`, and `useChannelTitle(channel)` hooks from `@asgard-js/react`, which bridge these stores into `useSyncExternalStore` for you.
435
+
400
436
  <a id="development"></a>
401
437
  <br/>
402
438
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asgard-js/core",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "dependencies": {
5
5
  "@microsoft/fetch-event-source": "^2.0.1",
6
6
  "rxjs": "^7.8.1",