@mercury-fw/channel-http 0.14.1 → 0.15.0

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
@@ -1,5 +1,25 @@
1
1
  # @mercury-fw/channel-http
2
2
 
3
+ ## 0.15.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 148417b: - a step whose tool asks the person to log in ends the turn: no further tool calls
8
+ - the turn's text closes with `core.login.note`, naming the services to log in to (`core.login.note-any` when none has a name)
9
+ - `detectLoginRequests(step)` in `@mercury-fw/channel-types`: every login a step asks for, one per link
10
+ - HTTP channel: one `login` event per service the step asks for
11
+ - Google Chat: one login card per service the step asks for
12
+ - **Migration:** apps with their own locale files add `core.login.note` (`{services}`) and `core.login.note-any` to each non-English one
13
+
14
+ ## 0.14.2
15
+
16
+ ### Patch Changes
17
+
18
+ - c4f4fd3: - a cancelled turn is archived: the person's message, then an answer marked `interrupted`
19
+ - `reads.conversation`: an interrupted answer comes with `interrupted: true` and an empty `content`
20
+ - `reads.conversations`: the preview skips an interrupted answer
21
+ - `GET /conversation` documents `interrupted` on `Message`
22
+
3
23
  ## 0.14.1
4
24
 
5
25
  ### Patch Changes
package/README.md CHANGED
@@ -60,7 +60,7 @@ Runs one conversational turn; the reply streams back as Server-Sent Events.
60
60
 
61
61
  **Time zone.** The model reads each message with the time the person wrote it, in their time zone: the one they saved, else the one the request's `X-Time-Zone` header names (an IANA name such as `Europe/Rome`, what `Intl.DateTimeFormat().resolvedOptions().timeZone` gives in a browser), else the app's. A UI saves the person's with `PATCH /me`.
62
62
 
63
- Reasoning and answer text arrive as **incremental deltas** — never one finished block — so a UI renders them live; treat the `final` event as the authoritative text. **To cancel** a turn (e.g. a generation that's diverging), close the connection: Mercury aborts the in-flight generation.
63
+ Reasoning and answer text arrive as **incremental deltas** — never one finished block — so a UI renders them live; treat the `final` event as the authoritative text. **To cancel** a turn (e.g. a generation that's diverging), close the connection: Mercury aborts the in-flight generation. Any connection that closes mid-turn cancels it, a page reload included. Nothing of the half answer is kept: once the model was answering, the conversation's transcript keeps your message and an answer marked `interrupted` (see `GET /conversation`); an opening, or a turn still waiting behind another, leaves nothing.
64
64
 
65
65
  **SSE events** — each is `event: <name>` followed by `data: <json>`:
66
66
 
@@ -72,7 +72,7 @@ Reasoning and answer text arrive as **incremental deltas** — never one finishe
72
72
  | `tool_finish` | `{ toolCallId, outcome }` | a tool call settles (`outcome`: `success` \| `failed` \| `pending`) |
73
73
  | `text` | `{ chunk }` | an answer-text delta |
74
74
  | `pending` | `{ command, token, choices }` | a confirm-required action was staged; send a token back to confirm it — as a later `/turn` `text`, or via `POST /confirm`. `choices` is every way to answer (`{ token, label }`, in order, each label in the caller's language; one with an empty `label` for a plain staging, which the UI words itself, a "Confirm" button): the others die with the one sent |
75
- | `login` | `{ service, url }` | the caller isn't logged in to `service` yet, which a plugin acts on as them: show them `url` to open (the model never gets it) |
75
+ | `login` | `{ service, url }` | the caller isn't logged in to `service` yet, which a plugin acts on as them: show them `url` to open (the model never gets it). One per service; the turn ends after the step that asked, its `final` text telling them to log in and write again |
76
76
  | `final` | `{ text, pendingNote? }` | the complete answer (also emitted for a token confirmation, with no model turn). `pendingNote` is there when the model wrote nothing because the turn staged an action: the core's stand-in sentence `text` begins with (then any list the turn surfaced). A UI showing its own confirmation hides it; the conversation's transcript keeps it |
77
77
  | `error` | `{ message }` | the turn failed mid-stream |
78
78
 
@@ -107,8 +107,8 @@ All `GET`, all JSON, all reporting state already held in-process. They need an a
107
107
 
108
108
  | endpoint | `data` on success |
109
109
  |---|---|
110
- | `GET /conversation?id=<conversationId>&limit=<n>&offset=<cursor>` | `{ messages: [{ role, content, timestamp }], nextOffset }` — one of your conversations, its durable transcript in order, each answer as you saw it with the lists it showed (empty for an id that isn't yours); `400` if `id` is missing or has other characters than `/turn` accepts |
111
- | `GET /conversations?limit=<n>` | `{ conversations: [{ conversationId, lastTimestamp, preview }] }` — your conversations, most-recently-active first |
110
+ | `GET /conversation?id=<conversationId>&limit=<n>&offset=<cursor>` | `{ messages: [{ role, content, interrupted?, timestamp }], nextOffset }` — one of your conversations, its durable transcript in order, each answer as you saw it with the lists it showed (empty for an id that isn't yours). An answer whose turn was cancelled has `interrupted: true` and an empty `content`: the question before it was left unanswered. `400` if `id` is missing or has other characters than `/turn` accepts |
111
+ | `GET /conversations?limit=<n>` | `{ conversations: [{ conversationId, lastTimestamp, preview }] }` — your conversations, most-recently-active first; the preview is the newest message that shows something, so the question when the last answer was interrupted |
112
112
  | `GET /manifest` | `{ manifest: { coreApiVersion, plugins: [{ name, apiVersion, active, skills, hasBuild, customStatus }], activeClis, skills } }` |
113
113
  | `GET /confirmations` | `{ pending: [{ sessionKey, summary, expiresAt }] }` — your pending confirmations; tokens are deliberately never included |
114
114
  | `GET /me` | `{ name, language, timeZone }` — how you asked the assistant to call you, the language you asked it to use and the time zone you saved, `null` when you never said; `language` and `timeZone` are missing on a core that doesn't know them, and the route isn't there on a core older than this channel |
@@ -117,7 +117,7 @@ All `GET`, all JSON, all reporting state already held in-process. They need an a
117
117
  | `GET /wiki/list` | `{ files: [...] }` — `curated/...` and `personal/...` paths |
118
118
  | `GET /wiki/read?path=<path>` | `{ content }` — a path as `/wiki/list` gives it; `400` if `path` is missing, `404` if it isn't among yours |
119
119
  | `GET /wiki/grep?pattern=<regex>` | `{ matches: [...] }` — `400` if `pattern` is missing |
120
- | `GET /memory/scroll?collection=<name>&limit=<n>&offset=<cursor>` | one page of your points in a collection kept per person (verbatim archive, episodic memory, semantic facts), each with its whole payload (a verbatim answer's `displays` and `calls` included); `400` for any other collection |
120
+ | `GET /memory/scroll?collection=<name>&limit=<n>&offset=<cursor>` | one page of your points in a collection kept per person (verbatim archive, episodic memory, semantic facts), each with its whole payload (a verbatim answer's `displays` and `calls` included, and an interrupted answer's `interrupted: true` with the note the model gets in its `content`); `400` for any other collection |
121
121
  | `GET /openapi.yaml` | the OpenAPI document for this surface (`text/yaml`) |
122
122
 
123
123
  Conversation history and the conversation list are backed by the durable verbatim archive; they degrade to empty when the vector store is unreachable.
package/http-server.ts CHANGED
@@ -23,7 +23,7 @@
23
23
  * package never imports the app.
24
24
  */
25
25
  import {
26
- detectLoginRequired,
26
+ detectLoginRequests,
27
27
  detectPendingConfirmation,
28
28
  type HandleTurn,
29
29
  type TurnSink,
@@ -262,8 +262,7 @@ export async function handleTurnRequest(req: Request, deps: TurnRequestDeps): Pr
262
262
  onStep: (step) => {
263
263
  const pending = detectPendingConfirmation(step);
264
264
  if (pending) send("pending", { command: pending.summary, token: pending.token, choices: pending.choices });
265
- const login = detectLoginRequired(step);
266
- if (login) send("login", { service: login.service, url: login.url });
265
+ for (const login of detectLoginRequests(step)) send("login", { service: login.service, url: login.url });
267
266
  },
268
267
  onUsage: () => {},
269
268
  // The core says when the text is its stand-in for an answer the
package/openapi.yaml CHANGED
@@ -132,7 +132,8 @@ paths:
132
132
  `tool_finish` (status of a tool call), `pending` (a staged
133
133
  confirm-required action, with its command, its token and the
134
134
  choices it offers), `login` (a
135
- service the caller has to log in to first, with the link to open),
135
+ service the caller has to log in to first, with the link to open;
136
+ one per service, and the turn ends after the step that asked),
136
137
  `final` (the
137
138
  authoritative complete answer), and `error`.
138
139
  headers:
@@ -825,6 +826,10 @@ components:
825
826
  content:
826
827
  type: string
827
828
  description: The message as the person saw it. For an answer, the lists the turn showed after it are included, as in the turn's `final` text.
829
+ interrupted:
830
+ type: boolean
831
+ enum: [true]
832
+ description: "On an answer whose turn was stopped before it was finished (the client closed the stream): `content` is empty. The question before it was left unanswered."
828
833
  timestamp:
829
834
  type: string
830
835
  description: ISO 8601, millisecond precision — the durable ordering key.
@@ -853,6 +858,7 @@ components:
853
858
  type: string
854
859
  preview:
855
860
  type: string
861
+ description: The newest message that shows something, as the person saw it, so the question when the last answer was interrupted.
856
862
  ConversationList:
857
863
  type: object
858
864
  required: [ok, conversations]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mercury-fw/channel-http",
3
- "version": "0.14.1",
3
+ "version": "0.15.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,11 +18,11 @@
18
18
  "access": "public"
19
19
  },
20
20
  "peerDependencies": {
21
- "@mercury-fw/channel-types": ">=0.65.0 <1.0.0"
21
+ "@mercury-fw/channel-types": ">=0.68.0 <1.0.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@mercury-fw/channel-types": "0.65.1",
25
- "@mercury-fw/plugin-types": "0.65.1",
24
+ "@mercury-fw/channel-types": "0.68.0",
25
+ "@mercury-fw/plugin-types": "0.68.0",
26
26
  "@mercury-fw/typescript-config": "*",
27
27
  "@types/bun": "^1.4.2",
28
28
  "typescript": "^6.0.3"