@vinaverse/client 0.1.0-alpha.10 → 0.1.0-alpha.12

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 (51) hide show
  1. package/README.md +22 -4
  2. package/dist/agent/index.cjs +43 -9
  3. package/dist/agent/index.cjs.map +1 -1
  4. package/dist/agent/index.d.cts +3 -3
  5. package/dist/agent/index.d.ts +3 -3
  6. package/dist/agent/index.js +1 -1
  7. package/dist/backend/index.cjs +37 -10
  8. package/dist/backend/index.cjs.map +1 -1
  9. package/dist/backend/index.d.cts +13 -3
  10. package/dist/backend/index.d.ts +13 -3
  11. package/dist/backend/index.js +38 -11
  12. package/dist/backend/index.js.map +1 -1
  13. package/dist/{chunk-O3NLAZGZ.js → chunk-5THD6XC7.js} +45 -11
  14. package/dist/chunk-5THD6XC7.js.map +1 -0
  15. package/dist/chunk-D56MNG3E.js +9 -0
  16. package/dist/{chunk-YNUHXJD6.js.map → chunk-D56MNG3E.js.map} +1 -1
  17. package/dist/contract-Be2oVHNr.d.cts +6 -0
  18. package/dist/contract-Be2oVHNr.d.ts +6 -0
  19. package/dist/conversation/index.cjs +2 -2
  20. package/dist/conversation/index.cjs.map +1 -1
  21. package/dist/conversation/index.d.cts +5 -5
  22. package/dist/conversation/index.d.ts +5 -5
  23. package/dist/conversation/index.js +1 -1
  24. package/dist/history/index.cjs +282 -0
  25. package/dist/history/index.cjs.map +1 -0
  26. package/dist/history/index.d.cts +91 -0
  27. package/dist/history/index.d.ts +91 -0
  28. package/dist/history/index.js +261 -0
  29. package/dist/history/index.js.map +1 -0
  30. package/dist/index.cjs +2 -2
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.cts +2 -2
  33. package/dist/index.d.ts +2 -2
  34. package/dist/index.js +1 -1
  35. package/dist/{runtime-Z1RI10lU.d.ts → runtime-BR2lS_Vg.d.cts} +26 -3
  36. package/dist/{runtime-CISPp9nJ.d.cts → runtime-CZyVCueC.d.ts} +26 -3
  37. package/dist/{session-CPz4pcPu.d.cts → session-D0ijOQX3.d.cts} +98 -5
  38. package/dist/{session-CPz4pcPu.d.ts → session-D0ijOQX3.d.ts} +98 -5
  39. package/dist/{store-DZka9T2-.d.ts → store-CGRSxVcL.d.ts} +1 -1
  40. package/dist/{store-BxiicOKG.d.cts → store-CxoHwd64.d.cts} +1 -1
  41. package/dist/testing/index.cjs +158 -14
  42. package/dist/testing/index.cjs.map +1 -1
  43. package/dist/testing/index.d.cts +42 -5
  44. package/dist/testing/index.d.ts +42 -5
  45. package/dist/testing/index.js +159 -15
  46. package/dist/testing/index.js.map +1 -1
  47. package/package.json +24 -1
  48. package/dist/chunk-O3NLAZGZ.js.map +0 -1
  49. package/dist/chunk-YNUHXJD6.js +0 -9
  50. package/dist/contract-DmQs8amJ.d.cts +0 -6
  51. package/dist/contract-DmQs8amJ.d.ts +0 -6
package/README.md CHANGED
@@ -139,7 +139,8 @@ app comes back to the foreground).
139
139
  **Your backend, in two lines** (`@vinaverse/client/backend`): `backendFromFetch({ routes, headers })` is an
140
140
  `AgentBackend` over your own routes, sending the person's credentials from `headers`, never in a body.
141
141
  `backendRoutes(base)` gives the routes the .NET SDK's `MapVinaverseAgentSessions` serves by default (`session`,
142
- `refresh`, `status`, `feedback`, `end`, `context`, `delete`); give your own where they differ. Both sides speak
142
+ `refresh`, `status`, `feedback`, `end`, `context`, `delete`, and history's `list` and `read`, answered
143
+ `{ version: 1, page }` as the gateway wrote the page); give your own where they differ. Both sides speak
143
144
  `vinaverse.agent-backend/1` (vinaverse-contracts `docs/agent-backend.md`, tested here against its fixture): successes
144
145
  wrapped with the version (`{ version: 1, session }`, `{ version: 1, client_secret: { value, expires_at } }`,
145
146
  `{ version: 1, status }`, or 204), and one failure body for every route. Every session creation (a first open, a
@@ -182,7 +183,9 @@ against the session it was given in; a reply restored from the device or a resum
182
183
  `{ screen: 'other' }`, no entity); revisions only grow across a conversation's sessions (`placeRevision` on the
183
184
  session request), a `context_conflict` (409, with `current_revision`) sends the newest place once more above it, only while the session
184
185
  and the person are the same, then says `moved`,
185
- and a telling lands only on the session it was told to. `onEvent` also says `message_sent`, `reply_completed` and
186
+ and a telling lands only on the session it was told to. One update is in flight per conversation: a place told meanwhile
187
+ (`show` and `tell` in one move, rapid moves) waits, only the newest, and goes next; a refusal that is no conflict is said
188
+ through `onWarning` (`context_refused`) in development builds. `onEvent` also says `message_sent`, `reply_completed` and
186
189
  `card_shown` (never the words), its `key` is a salted SHA-256 hash of the conversation's key (`hashKey`), never the person's id: stable per install
187
190
  (the salt is kept at `<persistenceKey>:salt` in your storage, a new one after `erase()`), and
188
191
  `session_opened` says where the chat was opened from (`entryPoint`: `launcher`, `ask`, `deep_link`, or your own).
@@ -192,8 +195,23 @@ For a mock mode and tests, `@vinaverse/client/testing` has a scripted agent:
192
195
  `createMockAgent({ script, reply, refuse, welcome })` gives an `AgentBackend` and a WebSocket factory that speaks the
193
196
  protocol (`conversation.transport.webSocket`). `script` is the replies in order, one per turn, each with its text, its
194
197
  cards or a failure; `reply` answers after it. `mockCards` gives the cards your tools would: `professional`,
195
- `workspace`, `timeSlots`, `proposal` (pending, confirmed in your app) and `links`. Scripted history comes with the
196
- runtime's history.
198
+ `workspace`, `timeSlots`, `proposal` (pending, confirmed in your app) and `links`. `history` scripts past
199
+ conversations (`{ id, title, state, items: [{ role, text, refusal? }, { card, proposal_outcome? }] }`): listed, read
200
+ back, continued with their messages, deleted (then `not_found`); a conversation begun in the mock is listed too once
201
+ something was said.
202
+
203
+ **History** (`@vinaverse/client/history`, an entry of its own): `createAgentHistory({ runtime, mode, scope, agent,
204
+ pageSize })` is the person's past conversations, newest activity first, a page at a time (`refresh`, `loadMore`, and
205
+ `retry` for the page that failed, the first or the next; `failure` says `off` when your backend serves no
206
+ `listConversations`; a cursor the gateway no longer takes, refused `unavailable` with its `invalid_request`, starts the
207
+ list over once, and any other refusal is said); `remove(key, id)` deletes through `runtime.deleteConversation` and drops
208
+ the row, one already gone too. `createAgentTranscript({ runtime, conversationId })` reads one back, the newest page
209
+ first and earlier pages prepended (`load`, `loadEarlier`, `retry`; a stale cursor reads back from the newest page
210
+ once), its items as the kits draw them (`historyItems`: messages
211
+ as sent, a cut-off reply and a guardrail's refusal kept, cards restored and inert, a proposal with the outcome it
212
+ had). `continueKey(conversation, subject)` is the key it continues under (its side and business as bound when it
213
+ started), for `runtime.continue` and `deleteConversation`. Nothing begun before a sign-out lands after it
214
+ (`runtime.epoch()`), nor a page from before a newer read.
197
215
 
198
216
  The contract's types are exported too (`paths`, `components`, `operations`, `Schemas`): import them instead of copying
199
217
  the spec. Design: [docs/architecture/agent-runtime.md](../../docs/architecture/agent-runtime.md).
@@ -2590,6 +2590,7 @@ var samePlace = (a, b) => a?.screen === b?.screen && (a?.entity?.type ?? null) =
2590
2590
  function usable(session, insecure) {
2591
2591
  return typeof session.id === "string" && session.id !== "" && (insecure.realtime === true || /^wss:\/\//i.test(session.realtime_url ?? "")) && (insecure.turns === true || /^https:\/\//i.test(session.turns_url ?? "")) && Boolean(session.client_secret);
2592
2592
  }
2593
+ var devBuild = () => globalThis.__DEV__ === true;
2593
2594
  function warnInsecure(insecure, warn) {
2594
2595
  if (!(insecure.realtime || insecure.turns)) return;
2595
2596
  if (globalThis.__DEV__ === true) return;
@@ -2908,6 +2909,8 @@ function createAgentRuntime(options) {
2908
2909
  place: null,
2909
2910
  told: null,
2910
2911
  revision: 0,
2912
+ telling: false,
2913
+ queued: false,
2911
2914
  entryPoint: null,
2912
2915
  expiresAt: null,
2913
2916
  shown: 0,
@@ -2942,26 +2945,55 @@ function createAgentRuntime(options) {
2942
2945
  update(entry, { moved });
2943
2946
  const updateContext = backend.updateContext?.bind(backend);
2944
2947
  if (!moved || !updateContext || sessionId === null) return;
2948
+ if (entry.telling) {
2949
+ entry.queued = true;
2950
+ return;
2951
+ }
2952
+ entry.telling = true;
2945
2953
  const asked = generation;
2946
2954
  const current2 = () => asked === generation && live.get(entry.key) === entry && entry.state.sessionId === sessionId;
2955
+ const settle = () => {
2956
+ entry.telling = false;
2957
+ if (!entry.queued) return;
2958
+ entry.queued = false;
2959
+ if (asked === generation && live.get(entry.key) === entry)
2960
+ tell(entry, {});
2961
+ };
2947
2962
  const send = (again) => {
2948
2963
  const place = entry.place;
2949
2964
  const revision = ++entry.revision;
2950
- updateContext(sessionId, place ?? CLEARED, revision).then(
2965
+ let told;
2966
+ try {
2967
+ told = updateContext(sessionId, place ?? CLEARED, revision);
2968
+ } catch (error) {
2969
+ told = Promise.reject(error);
2970
+ }
2971
+ told.then(
2951
2972
  () => {
2952
- if (!current2() || entry.revision !== revision) return;
2953
- entry.told = place;
2954
- update(entry, { moved: !samePlace(entry.told, entry.place) });
2955
- emit({ type: "context_updated", key: entry.key, sessionId });
2973
+ if (current2() && entry.revision === revision) {
2974
+ entry.told = place;
2975
+ update(entry, { moved: !samePlace(entry.told, entry.place) });
2976
+ emit({ type: "context_updated", key: entry.key, sessionId });
2977
+ }
2978
+ settle();
2956
2979
  },
2957
2980
  (error) => {
2958
2981
  const theirs = field(error, "currentRevision");
2959
2982
  const conflict = field(error, "outcome") === "context_conflict" || field(error, "reason") === "conflict";
2960
- if (!conflict || again || !current2() || entry.revision !== revision)
2983
+ if (conflict && !again && current2() && entry.revision === revision) {
2984
+ if (typeof theirs === "number" && theirs >= entry.revision)
2985
+ entry.revision = theirs;
2986
+ send(true);
2961
2987
  return;
2962
- if (typeof theirs === "number" && theirs >= entry.revision)
2963
- entry.revision = theirs;
2964
- send(true);
2988
+ }
2989
+ if (!conflict && devBuild()) {
2990
+ const status2 = field(error, "status");
2991
+ warn(
2992
+ `@vinaverse/client/agent: updateContext was refused${typeof status2 === "number" ? ` (${status2})` : ""}: ${error instanceof Error ? error.message : String(error)}. The new place is offered as a question instead.`,
2993
+ { code: "context_refused" }
2994
+ );
2995
+ }
2996
+ settle();
2965
2997
  }
2966
2998
  );
2967
2999
  };
@@ -3126,6 +3158,8 @@ function createAgentRuntime(options) {
3126
3158
  },
3127
3159
  getStatus: () => status?.value ?? null,
3128
3160
  getStatusState: () => statusState,
3161
+ backend,
3162
+ epoch: () => generation,
3129
3163
  getName: () => sessionName ?? status?.value.displayName ?? null,
3130
3164
  subscribe(listener) {
3131
3165
  listeners.add(listener);