@asgard-js/core 0.3.94 → 0.3.95

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/dist/index.mjs CHANGED
@@ -1669,14 +1669,34 @@ class Yn {
1669
1669
  * about a minute), on purpose: the next turn must not race a dying pod.
1670
1670
  */
1671
1671
  async deleteChannel(t) {
1672
- const e = this.deriveChannelEndpoint();
1673
- if (!e)
1672
+ await this.releaseChannel("DELETE", "", t);
1673
+ }
1674
+ /**
1675
+ * Reset the conversation but keep the channel's files (F-039): `POST {base}/channel/clear?custom_channel_id=…`.
1676
+ * The backend stops the run and clears the transcript, agent session, title and pending tool consent,
1677
+ * but keeps the Channel Home (`/work`) files, uploaded blobs, the tool-call allow-list and the channel
1678
+ * itself; the Sandbox restarts onto the same files. It answers only once the clear is done.
1679
+ *
1680
+ * Same success rule as {@link deleteChannel}: **any 2xx**, body never read — the edge answers `204`, a
1681
+ * BFF relay may answer `200` with a body. There is no `404` special case either: an absent channel
1682
+ * answers 2xx, so a `404` means the relay route does not exist, and reading it as "cleared" would hide
1683
+ * a clear that never happened. Everything else throws {@link HttpError}.
1684
+ *
1685
+ * **No timeout is imposed** — a live Sandbox can hold the answer for up to about 45 seconds.
1686
+ */
1687
+ async clearChannel(t) {
1688
+ await this.releaseChannel("POST", "/clear", t);
1689
+ }
1690
+ /** The shared request behind {@link deleteChannel} and {@link clearChannel}: any 2xx resolves, else throws. */
1691
+ async releaseChannel(t, e, s) {
1692
+ const r = this.deriveChannelEndpoint();
1693
+ if (!r)
1674
1694
  throw new Error("Unable to derive channel endpoint. Please provide botProviderEndpoint in config.");
1675
- const s = new URL(e);
1676
- s.searchParams.set("custom_channel_id", t);
1677
- const r = await fetch(s.toString(), { method: "DELETE", headers: this.apiHeaders() });
1678
- if (!r.ok)
1679
- throw new k(r.status, r.statusText, await r.text().catch(() => {
1695
+ const i = new URL(`${r}${e}`);
1696
+ i.searchParams.set("custom_channel_id", s);
1697
+ const o = await fetch(i.toString(), { method: t, headers: this.apiHeaders() });
1698
+ if (!o.ok)
1699
+ throw new k(o.status, o.statusText, await o.text().catch(() => {
1680
1700
  }));
1681
1701
  }
1682
1702
  deriveFeedbackEndpoint() {
@@ -3043,8 +3063,11 @@ class q {
3043
3063
  * Clear an **existing** conversation and start over on the same id (F-032) — what the header's reset
3044
3064
  * button does. Two steps, in this order and never bundled:
3045
3065
  *
3046
- * 1. `client.deleteChannel()` — the backend releases the run, transcript, blobs, consent allow-list,
3047
- * Sandbox and Channel Home, and answers only once that teardown is done.
3066
+ * 1. Release the old conversation, per `mode` (F-039, default `'delete'`):
3067
+ * - `'delete'` — `client.deleteChannel()`: the backend releases the run, transcript, blobs, consent
3068
+ * allow-list, Sandbox and Channel Home, and answers only once that teardown is done.
3069
+ * - `'clear'` — `client.clearChannel()`: the conversation is cleared but the Channel Home files,
3070
+ * blobs and allow-list are kept; the Sandbox restarts onto the same files.
3048
3071
  * 2. {@link Channel.open} — a plain `action=NONE` opening turn on the now-empty id.
3049
3072
  *
3050
3073
  * This replaces the single `RESET_CHANNEL` turn, which bundled both into one request and therefore
@@ -3052,17 +3075,27 @@ class q {
3052
3075
  * the delete stripped them and the `blobIds` resolved to nothing without an error. A host that needs
3053
3076
  * "clear, then send with attachments" sequences it itself: `deleteChannel` → upload → `sendMessage`.
3054
3077
  *
3055
- * **Nothing local changes until the delete succeeds** — no channel is constructed, so no state
3056
- * observer fires. A failed delete therefore leaves the caller's current channel and conversation
3078
+ * **Nothing local changes until step 1 succeeds** — no channel is constructed, so no state
3079
+ * observer fires. A failed delete or clear therefore leaves the caller's current channel and conversation
3057
3080
  * exactly as they were, which is the point: the screen must never be cleared while the server still
3058
3081
  * holds the old conversation.
3059
3082
  */
3060
- static async reset(t, e, s, r) {
3061
- if (!t.client.deleteChannel)
3062
- throw new Error(
3063
- "This client cannot reset a channel: IAsgardServiceClient.deleteChannel is not implemented. Implement it (DELETE {base}/channel?custom_channel_id=…) or use AsgardServiceClient."
3064
- );
3065
- return await t.client.deleteChannel(t.customChannelId), q.open(t, e, s, r);
3083
+ static async reset(t, e, s, r, i = "delete") {
3084
+ const { client: o, customChannelId: a } = t;
3085
+ if (i === "clear") {
3086
+ if (!o.clearChannel)
3087
+ throw new Error(
3088
+ "This client cannot clear a channel: IAsgardServiceClient.clearChannel is not implemented. Implement it (POST {base}/channel/clear?custom_channel_id=…) or use AsgardServiceClient."
3089
+ );
3090
+ await o.clearChannel(a);
3091
+ } else {
3092
+ if (!o.deleteChannel)
3093
+ throw new Error(
3094
+ "This client cannot reset a channel: IAsgardServiceClient.deleteChannel is not implemented. Implement it (DELETE {base}/channel?custom_channel_id=…) or use AsgardServiceClient."
3095
+ );
3096
+ await o.deleteChannel(a);
3097
+ }
3098
+ return q.open(t, e, s, r);
3066
3099
  }
3067
3100
  /**
3068
3101
  * Join an **existing** channel without resetting it (F-015). Seeds the title from `config.channelTitle`