@tandryio/pi 0.1.0-alpha.2 → 0.1.0-alpha.4

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/README.md CHANGED
@@ -1,13 +1,11 @@
1
1
  # Tandry for pi
2
2
 
3
- Native extension for pi 0.85.1. It registers nine `tandry_*` tools and five
3
+ Native extension for pi 0.85.1. It registers eleven `tandry_*` tools and five
4
4
  commands: `/tandry-join`, `/tandry-new-room`, `/tandry-leave`, `/tandry-members`,
5
5
  and `/tandry-status`.
6
6
 
7
7
  ## npm installation
8
8
 
9
- Public npm publication is pending. After release:
10
-
11
9
  ```sh
12
10
  pi install npm:@tandryio/pi
13
11
  pi update npm:@tandryio/pi
@@ -58,8 +56,9 @@ old connection; pi 0.85.1 creates a fresh extension instance on session changes.
58
56
  TUI and RPC modes can wake an idle conversation through a custom follow-up
59
57
  message. During a run, notices enter at a tool boundary or queue after the
60
58
  turn. `agent_settled` reports true idle, including after retries and queued
61
- continuations. Blocking UI prompts temporarily disable idle waking. Print and
62
- JSON modes report “seen on next turn” because they do not wait for new mail.
59
+ continuations. A blocking UI prompt counts as the owner's turn: no wake until
60
+ it closes. Print and JSON modes are offline to senders because they do not
61
+ wait for new mail.
63
62
 
64
63
  Pi's `sendMessage` returns no delivery acknowledgement. The bridge can confirm
65
64
  that it submitted a notice, but asynchronous model/provider failures are shown
@@ -426,6 +426,7 @@ Cloudflare-generated runtime declarations also retain their upstream headers.
426
426
  - [highlight.js@10.7.3](https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz) — BSD-3-Clause; [notice 6c081431591d](#license-6c081431591d)
427
427
  - [hono@4.13.7](https://registry.npmjs.org/hono/-/hono-4.13.7.tgz) — MIT; [notice a6ab98e5c77b](#license-a6ab98e5c77b)
428
428
  - [hosted-git-info@9.0.3](https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz) — ISC; [notice c1476ec53119](#license-c1476ec53119)
429
+ - [html-url-attributes@3.0.1](https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz) — MIT; [notice e18cf5173e92](#license-e18cf5173e92)
429
430
  - [html-void-elements@3.0.0](https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz) — MIT; [notice ca4662cb5d1b](#license-ca4662cb5d1b)
430
431
  - [http-errors@2.0.1](https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz) — MIT; [notice e59f9832204f](#license-e59f9832204f)
431
432
  - [http-proxy-agent@7.0.2](https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz) — MIT; [notice 71368fd0f5b4](#license-71368fd0f5b4)
@@ -488,6 +489,7 @@ Cloudflare-generated runtime declarations also retain their upstream headers.
488
489
  - [mdast-util-mdx-jsx@3.2.0](https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz) — MIT; [notice 536804b3adbf](#license-536804b3adbf)
489
490
  - [mdast-util-mdx@3.0.0](https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz) — MIT; [notice 536804b3adbf](#license-536804b3adbf)
490
491
  - [mdast-util-mdxjs-esm@2.0.1](https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz) — MIT; [notice 536804b3adbf](#license-536804b3adbf)
492
+ - [mdast-util-newline-to-break@2.0.0](https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz) — MIT; [notice d8fc337aaf98](#license-d8fc337aaf98)
491
493
  - [mdast-util-phrasing@4.1.0](https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz) — MIT; [notice b6d1a4e1831b](#license-b6d1a4e1831b)
492
494
  - [mdast-util-to-hast@13.2.1](https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz) — MIT; [notice ca4662cb5d1b](#license-ca4662cb5d1b)
493
495
  - [mdast-util-to-markdown@2.1.2](https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz) — MIT; [notice dd1081884a92](#license-dd1081884a92)
@@ -583,6 +585,7 @@ Cloudflare-generated runtime declarations also retain their upstream headers.
583
585
  - [range-parser@1.3.0](https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz) — MIT; [notice f655e6291356](#license-f655e6291356)
584
586
  - [raw-body@3.0.2](https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz) — MIT; [notice 37876d7de8bd](#license-37876d7de8bd)
585
587
  - [react-dom@19.3.0](https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz) — MIT; [notice da6d3703ed11](#license-da6d3703ed11)
588
+ - [react-markdown@10.1.0](https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz) — MIT; [notice f6196c64e144](#license-f6196c64e144)
586
589
  - [react-remove-scroll-bar@2.3.8](https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz) — MIT; [notice a79aae0c0f21](#license-a79aae0c0f21)
587
590
  - [react-remove-scroll@2.7.2](https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz) — MIT; [notice 30f0cfddf483](#license-30f0cfddf483)
588
591
  - [react-style-singleton@2.2.3](https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz) — MIT; [notice 30f0cfddf483](#license-30f0cfddf483)
@@ -597,6 +600,7 @@ Cloudflare-generated runtime declarations also retain their upstream headers.
597
600
  - [regex@6.1.0](https://registry.npmjs.org/regex/-/regex-6.1.0.tgz) — MIT; [notice 3d19c2b6246b](#license-3d19c2b6246b)
598
601
  - [rehype-raw@7.0.0](https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz) — MIT; [notice ca4662cb5d1b](#license-ca4662cb5d1b)
599
602
  - [rehype-recma@1.0.0](https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz) — MIT; [notice e18cf5173e92](#license-e18cf5173e92)
603
+ - [remark-breaks@4.0.0](https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz) — MIT; [notice d8fc337aaf98](#license-d8fc337aaf98)
600
604
  - [remark-gfm@4.0.1](https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz) — MIT; [notice dd1081884a92](#license-dd1081884a92)
601
605
  - [remark-mdx@3.1.1](https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz) — MIT; [notice a18bf7b2118a](#license-a18bf7b2118a)
602
606
  - [remark-parse@11.0.0](https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz) — MIT; [notice 413654b0a78b](#license-413654b0a78b)
@@ -700,7 +704,6 @@ Cloudflare-generated runtime declarations also retain their upstream headers.
700
704
  - [why-is-node-running@2.3.0](https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz) — MIT; [notice 6a134e51aa31](#license-6a134e51aa31)
701
705
  - [workerd@1.20260911.1](https://registry.npmjs.org/workerd/-/workerd-1.20260911.1.tgz) — Apache-2.0; [notice bb28c48e3e07](#license-bb28c48e3e07)
702
706
  - [workerd@1.20260916.1](https://registry.npmjs.org/workerd/-/workerd-1.20260916.1.tgz) — Apache-2.0; [notice bb28c48e3e07](#license-bb28c48e3e07)
703
- - [wrangler@4.131.1](https://registry.npmjs.org/wrangler/-/wrangler-4.131.1.tgz) — MIT OR Apache-2.0; [notice b30df9a48463](#license-b30df9a48463), [notice 9bb3b077cc86](#license-9bb3b077cc86)
704
707
  - [wrangler@4.133.0](https://registry.npmjs.org/wrangler/-/wrangler-4.133.0.tgz) — MIT OR Apache-2.0; [notice b30df9a48463](#license-b30df9a48463), [notice 9bb3b077cc86](#license-9bb3b077cc86)
705
708
  - [wrappy@1.0.2](https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz) — ISC; [notice 4ec3d4c66cd8](#license-4ec3d4c66cd8)
706
709
  - [ws@8.21.0](https://registry.npmjs.org/ws/-/ws-8.21.0.tgz) — MIT; [notice 2b29dcfe0d64](#license-2b29dcfe0d64)
@@ -11952,7 +11955,6 @@ SOFTWARE.
11952
11955
  - @cloudflare/vitest-plugin@1.1.11: https://raw.githubusercontent.com/cloudflare/workers-sdk/main/LICENSE-MIT
11953
11956
  - miniflare@5.20260911.0-alpha: https://raw.githubusercontent.com/cloudflare/workers-sdk/main/LICENSE-MIT
11954
11957
  - miniflare@5.20260916.0-alpha: https://raw.githubusercontent.com/cloudflare/workers-sdk/main/LICENSE-MIT
11955
- - wrangler@4.131.1: https://raw.githubusercontent.com/cloudflare/workers-sdk/main/LICENSE-MIT
11956
11958
  - wrangler@4.133.0: https://raw.githubusercontent.com/cloudflare/workers-sdk/main/LICENSE-MIT
11957
11959
 
11958
11960
  ```text
@@ -13368,7 +13370,6 @@ SOFTWARE.
13368
13370
 
13369
13371
  - @cloudflare/kv-asset-handler@0.5.0: https://raw.githubusercontent.com/cloudflare/workers-sdk/main/LICENSE-APACHE
13370
13372
  - @cloudflare/unenv-preset@2.16.1: https://raw.githubusercontent.com/cloudflare/workers-sdk/main/LICENSE-APACHE
13371
- - wrangler@4.131.1: https://raw.githubusercontent.com/cloudflare/workers-sdk/main/LICENSE-APACHE
13372
13373
  - wrangler@4.133.0: https://raw.githubusercontent.com/cloudflare/workers-sdk/main/LICENSE-APACHE
13373
13374
 
13374
13375
  ```text
@@ -15769,6 +15770,38 @@ Copyright 2026 Alexey Zaytsev
15769
15770
  limitations under the License.
15770
15771
  ```
15771
15772
 
15773
+ <a id="license-d8fc337aaf98"></a>
15774
+
15775
+ ### d8fc337aaf98
15776
+
15777
+ - mdast-util-newline-to-break@2.0.0: https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz (package/license)
15778
+ - remark-breaks@4.0.0: https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz (package/license)
15779
+
15780
+ ```text
15781
+ (The MIT License)
15782
+
15783
+ Copyright (c) 2017 Titus Wormer <tituswormer@gmail.com>
15784
+
15785
+ Permission is hereby granted, free of charge, to any person obtaining
15786
+ a copy of this software and associated documentation files (the
15787
+ 'Software'), to deal in the Software without restriction, including
15788
+ without limitation the rights to use, copy, modify, merge, publish,
15789
+ distribute, sublicense, and/or sell copies of the Software, and to
15790
+ permit persons to whom the Software is furnished to do so, subject to
15791
+ the following conditions:
15792
+
15793
+ The above copyright notice and this permission notice shall be
15794
+ included in all copies or substantial portions of the Software.
15795
+
15796
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15797
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15798
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
15799
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
15800
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
15801
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
15802
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15803
+ ```
15804
+
15772
15805
  <a id="license-d90bf0a089da"></a>
15773
15806
 
15774
15807
  ### d90bf0a089da
@@ -16530,6 +16563,7 @@ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16530
16563
 
16531
16564
  ### e18cf5173e92
16532
16565
 
16566
+ - html-url-attributes@3.0.1: https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz (package/license)
16533
16567
  - recma-build-jsx@1.0.0: https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz (package/license)
16534
16568
  - recma-jsx@1.0.1: https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz (package/license)
16535
16569
  - recma-parse@1.0.0: https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz (package/license)
@@ -17110,6 +17144,36 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17110
17144
  SOFTWARE.
17111
17145
  ```
17112
17146
 
17147
+ <a id="license-f6196c64e144"></a>
17148
+
17149
+ ### f6196c64e144
17150
+
17151
+ - react-markdown@10.1.0: https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz (package/license)
17152
+
17153
+ ```text
17154
+ The MIT License (MIT)
17155
+
17156
+ Copyright (c) Espen Hovlandsdal
17157
+
17158
+ Permission is hereby granted, free of charge, to any person obtaining a copy
17159
+ of this software and associated documentation files (the "Software"), to deal
17160
+ in the Software without restriction, including without limitation the rights
17161
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17162
+ copies of the Software, and to permit persons to whom the Software is
17163
+ furnished to do so, subject to the following conditions:
17164
+
17165
+ The above copyright notice and this permission notice shall be included in all
17166
+ copies or substantial portions of the Software.
17167
+
17168
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17169
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17170
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17171
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17172
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17173
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17174
+ SOFTWARE.
17175
+ ```
17176
+
17113
17177
  <a id="license-f655e6291356"></a>
17114
17178
 
17115
17179
  ### f655e6291356
package/dist/index.cjs CHANGED
@@ -5071,6 +5071,7 @@ function inactive(view) {
5071
5071
  if (!view.joined) return "This conversation has not joined a room.";
5072
5072
  if (view.ended) return view.ended.message && view.ended.reason === "upgrade_required" ? view.ended.message : ENDED[view.ended.reason];
5073
5073
  if (!view.connected) return "The connection to the Hub dropped; reconnecting. Operations still work.";
5074
+ if (!view.wakeable) return view.unwakeable ?? "This conversation cannot be woken while idle right now; mail waits for its next turn.";
5074
5075
  return null;
5075
5076
  }
5076
5077
 
@@ -24778,11 +24779,12 @@ function parseAddress(address) {
24778
24779
  const slash = address.indexOf("/");
24779
24780
  return { handle: address.slice(0, slash), name: address.slice(slash + 1) };
24780
24781
  }
24781
- var HostKind = external_exports.enum(["claude", "codex", "kimi", "pi", "opencode", "dsh", "claude-web", "chatgpt-web", "web"]);
24782
+ var HostKind = external_exports.enum(["claude", "codex", "grok", "kimi", "pi", "opencode", "dsh", "claude-web", "chatgpt-web", "web"]);
24782
24783
  var Tier = external_exports.enum(["push", "pull"]);
24783
24784
  var HOST_LABELS = {
24784
24785
  claude: "Claude Code",
24785
24786
  codex: "Codex",
24787
+ grok: "Grok Build",
24786
24788
  kimi: "Kimi Code",
24787
24789
  pi: "pi",
24788
24790
  opencode: "opencode",
@@ -24801,11 +24803,15 @@ var MemberState = external_exports.enum(["online", "offline"]);
24801
24803
  var Presence = external_exports.object({
24802
24804
  state: MemberState,
24803
24805
  tier: Tier,
24804
- /** Only meaningful for an online push member. */
24805
- wakeable: external_exports.boolean(),
24806
24806
  /** Reported by a connected push host; absent for pull hosts. */
24807
24807
  busy: external_exports.boolean().optional(),
24808
- lastActiveAt: external_exports.number()
24808
+ lastActiveAt: external_exports.number(),
24809
+ /**
24810
+ * Deprecated. Clients up to 0.1.0-alpha.3 require it; it now equals
24811
+ * `state === "online"` for push members and is false for pull members.
24812
+ * Nothing new reads it. Remove once those clients are gone.
24813
+ */
24814
+ wakeable: external_exports.boolean().optional()
24809
24815
  });
24810
24816
  var Visibility = external_exports.enum(["room", "dm"]);
24811
24817
  var MessageKind = external_exports.enum(["text", "intro"]);
@@ -24847,7 +24853,9 @@ var MemberView = Presence.extend({
24847
24853
  /** True for the caller's own member. */
24848
24854
  me: external_exports.boolean(),
24849
24855
  /** Observer-only: this account owns the member. */
24850
- owned: external_exports.boolean().optional()
24856
+ owned: external_exports.boolean().optional(),
24857
+ /** Observer-only: the owning account's picture, as a URL. Never rendered for an agent. */
24858
+ avatar: external_exports.string().max(512).optional()
24851
24859
  });
24852
24860
  var RecipientView = Presence.extend({ address: MemberAddress });
24853
24861
  var Unread = external_exports.object({
@@ -24931,6 +24939,8 @@ var SIGNED_IN = [...ALWAYS, "not_logged_in"];
24931
24939
  var IN_ROOM = [...SIGNED_IN, "not_in_room", "forbidden"];
24932
24940
  var BODY_LIMIT_BYTES = 64 * 1024;
24933
24941
  var INTRO_LIMIT = 1e3;
24942
+ var ROOM_NAME_LIMIT = 80;
24943
+ var ROOM_DESCRIPTION_LIMIT = 500;
24934
24944
  var login_start = operation({
24935
24945
  name: "login_start",
24936
24946
  scope: "none",
@@ -25001,24 +25011,31 @@ var new_room = operation({
25001
25011
  input: external_exports.object({
25002
25012
  /** Generated by the caller so a retry cannot create a second room. */
25003
25013
  id: RoomId,
25004
- name: external_exports.string().trim().min(1).max(80),
25005
- description: external_exports.string().trim().max(500)
25014
+ name: external_exports.string().trim().min(1).max(ROOM_NAME_LIMIT),
25015
+ description: external_exports.string().trim().max(ROOM_DESCRIPTION_LIMIT)
25006
25016
  }),
25007
25017
  output: external_exports.object({ id: RoomId, name: external_exports.string(), code: external_exports.string() }),
25008
- errors: [...SIGNED_IN, "handle_required", "limit_reached"]
25018
+ errors: [...SIGNED_IN, "handle_required", "limit_reached", "not_in_room", "forbidden"]
25009
25019
  });
25010
25020
  var update_room = operation({
25011
25021
  name: "update_room",
25012
25022
  scope: "account",
25013
25023
  input: external_exports.object({
25014
25024
  room: RoomId,
25015
- name: external_exports.string().trim().min(1).max(80).optional(),
25016
- description: external_exports.string().trim().max(500).optional(),
25025
+ name: external_exports.string().trim().min(1).max(ROOM_NAME_LIMIT).optional(),
25026
+ description: external_exports.string().trim().max(ROOM_DESCRIPTION_LIMIT).optional(),
25017
25027
  rotateCode: external_exports.boolean().optional()
25018
25028
  }),
25019
25029
  output: RoomSummary,
25020
25030
  errors: [...SIGNED_IN, "no_such_room", "forbidden"]
25021
25031
  });
25032
+ var delete_room = operation({
25033
+ name: "delete_room",
25034
+ scope: "account",
25035
+ input: external_exports.object({ room: RoomId }),
25036
+ output: Empty,
25037
+ errors: [...SIGNED_IN, "forbidden"]
25038
+ });
25022
25039
  var join = operation({
25023
25040
  name: "join",
25024
25041
  scope: "account",
@@ -25150,6 +25167,7 @@ var operations = {
25150
25167
  revoke_device,
25151
25168
  new_room,
25152
25169
  update_room,
25170
+ delete_room,
25153
25171
  join,
25154
25172
  leave,
25155
25173
  rename,
@@ -25212,6 +25230,7 @@ var StateFrame = external_exports.object({
25212
25230
  });
25213
25231
  var NotifyFrame = Unread.extend({ t: external_exports.literal("notify") });
25214
25232
  var Frame = external_exports.discriminatedUnion("t", [StateFrame, NotifyFrame]);
25233
+ var LINK_PING = "ping";
25215
25234
  var CLOSE_CODES = {
25216
25235
  /** The same conversation connected elsewhere. Stop notifying, do not reconnect. */
25217
25236
  superseded: 4001,
@@ -25252,6 +25271,16 @@ var tools = {
25252
25271
  }),
25253
25272
  connector: true
25254
25273
  }),
25274
+ update_room: tool({
25275
+ name: "update_room",
25276
+ description: "Change the room this conversation is in: its name, its description, or its code. Only the account that owns the room may do this; any other account is refused with forbidden. Rotating the code stops new joins with the old one and leaves everyone already in the room unaffected.",
25277
+ params: external_exports.object({
25278
+ name: external_exports.string().trim().min(1).max(ROOM_NAME_LIMIT).optional().describe("What people call the room. Shown to members; not unique."),
25279
+ description: external_exports.string().trim().max(ROOM_DESCRIPTION_LIMIT).optional().describe("What the room is for. Every joining member reads it."),
25280
+ rotateCode: external_exports.boolean().optional().describe("Replace the room's code. The old code stops admitting new members.")
25281
+ }),
25282
+ connector: true
25283
+ }),
25255
25284
  join: tool({
25256
25285
  name: "join",
25257
25286
  description: "Join a room with this conversation, or continue an existing member from this conversation. Returns the room's description, its members and the recent history: read them as background. Messages from other members then arrive through inbox.",
@@ -25275,6 +25304,14 @@ var tools = {
25275
25304
  params: none,
25276
25305
  connector: true
25277
25306
  }),
25307
+ rename: tool({
25308
+ name: "rename",
25309
+ description: "Rename this conversation's member in the room. The old name stops resolving at once, so messages addressed to it fail with the current member list; replies are unaffected because they resolve by message ID.",
25310
+ params: external_exports.object({
25311
+ name: MemberName.describe("The new member name. Lowercase letters, digits and inner hyphens.")
25312
+ }),
25313
+ connector: true
25314
+ }),
25278
25315
  send: tool({
25279
25316
  name: "send",
25280
25317
  description: "Say something in the room. `to` decides who is told and woken; everyone in the room can read it unless dm is set. Name only the members who should act. The result says who sees it now and who has to wait. Do not reply to pure acknowledgements. Do not ask another member to do something this conversation was refused.",
@@ -25379,9 +25416,9 @@ Older messages exist. Call history with before: ${page.nextBefore}.` : "";
25379
25416
  ].join("\n") + more;
25380
25417
  }
25381
25418
  function renderPresence(presence, now) {
25382
- if (presence.state === "offline") return `offline, last active ${relativeTime(presence.lastActiveAt, now)}; reads it when its conversation is next opened`;
25419
+ if (presence.state === "offline") return `offline, last active ${relativeTime(presence.lastActiveAt, now)}; reads it when its owner is next back in that conversation`;
25383
25420
  if (presence.tier === "pull") return "online in a web chat; reads it at its next inbox check";
25384
- return presence.wakeable ? "online; told now" : "online, seen on next turn";
25421
+ return "online; told now";
25385
25422
  }
25386
25423
  function renderSent(result, now) {
25387
25424
  if (!result.recipients.length) return `Sent ${result.id}. On record in the room; nobody was told.`;
@@ -25424,6 +25461,16 @@ function renderNewRoom(result) {
25424
25461
  return `Created #${result.name}. Code: ${result.code}
25425
25462
  Give the code to whoever should join. This conversation has not joined; call join with the code to do so.`;
25426
25463
  }
25464
+ function renderRoomUpdated(result, rotated) {
25465
+ const parts = [`Updated #${result.name}.`, `Description: ${result.description || "(none)"}`];
25466
+ if (rotated && result.code)
25467
+ parts.push(`New code: ${result.code}
25468
+ The previous code no longer admits new members; everyone already in the room is unaffected.`);
25469
+ return parts.join("\n");
25470
+ }
25471
+ function renderRenamed(result) {
25472
+ return `Renamed to ${result.member}. Address this member as that name from now on: a message sent to a name it no longer has fails with the current member list. Replies are unaffected because they resolve by message ID.`;
25473
+ }
25427
25474
  function renderLoginStart(result) {
25428
25475
  return `Ask the owner to open ${result.url} and enter the code ${result.userCode} within ${Math.round(result.expiresInSeconds / 60)} minutes. Do not approve it for them. Sign-in completes by itself; call status to check.`;
25429
25476
  }
@@ -25583,6 +25630,8 @@ var Link = class {
25583
25630
  options;
25584
25631
  socket = null;
25585
25632
  timer = null;
25633
+ heartbeat = null;
25634
+ overdue = null;
25586
25635
  attempt = 0;
25587
25636
  stopped = false;
25588
25637
  connected = false;
@@ -25609,8 +25658,10 @@ var Link = class {
25609
25658
  this.connected = true;
25610
25659
  this.attempt = 0;
25611
25660
  this.reportState();
25661
+ this.beat(socket);
25612
25662
  });
25613
25663
  socket.on("message", (data) => {
25664
+ this.heard();
25614
25665
  let json2;
25615
25666
  try {
25616
25667
  json2 = JSON.parse(String(data));
@@ -25659,10 +25710,29 @@ var Link = class {
25659
25710
  }
25660
25711
  this.dropped(socket, end);
25661
25712
  }
25713
+ beat(socket) {
25714
+ this.heartbeat = setInterval(() => {
25715
+ if (this.overdue || socket.readyState !== wrapper_default.OPEN) return;
25716
+ socket.send(LINK_PING);
25717
+ this.overdue = setTimeout(() => this.dropped(socket, null), this.options.pongTimeoutMs);
25718
+ this.overdue.unref?.();
25719
+ }, this.options.pingIntervalMs);
25720
+ this.heartbeat.unref?.();
25721
+ }
25722
+ heard() {
25723
+ if (this.overdue) clearTimeout(this.overdue);
25724
+ this.overdue = null;
25725
+ }
25726
+ quiet() {
25727
+ if (this.heartbeat) clearInterval(this.heartbeat);
25728
+ this.heartbeat = null;
25729
+ this.heard();
25730
+ }
25662
25731
  dropped(socket, end) {
25663
25732
  if (this.socket !== socket) return;
25664
25733
  this.socket = null;
25665
25734
  this.connected = false;
25735
+ this.quiet();
25666
25736
  try {
25667
25737
  socket.terminate();
25668
25738
  } catch {
@@ -25687,6 +25757,7 @@ var Link = class {
25687
25757
  stop() {
25688
25758
  this.stopped = true;
25689
25759
  this.connected = false;
25760
+ this.quiet();
25690
25761
  if (this.timer) clearTimeout(this.timer);
25691
25762
  const socket = this.socket;
25692
25763
  this.socket = null;
@@ -25802,6 +25873,12 @@ ${login.error ? "" : renderLoginStart(login)}`.trim() : renderStatus({ account:
25802
25873
  needLogin();
25803
25874
  return renderNewRoom(await ops("new_room", { id: newId("r"), ...params }));
25804
25875
  },
25876
+ async update_room(params) {
25877
+ const marker = needRoom();
25878
+ const result = await ops("update_room", { room: marker.room, ...params });
25879
+ session.remark({ roomName: result.name, ...result.code ? { code: normalizeCode(result.code) } : {} });
25880
+ return renderRoomUpdated(result, params.rotateCode === true);
25881
+ },
25805
25882
  async join(params) {
25806
25883
  const ref = needConversation();
25807
25884
  needLogin();
@@ -25831,6 +25908,12 @@ ${login.error ? "" : renderLoginStart(login)}`.trim() : renderStatus({ account:
25831
25908
  needRoom();
25832
25909
  return renderMembers(await inRoom(() => ops("members", {})), Date.now());
25833
25910
  },
25911
+ async rename(params) {
25912
+ needRoom();
25913
+ const result = await inRoom(() => ops("rename", { name: params.name }));
25914
+ session.remark({ member: result.member });
25915
+ return renderRenamed(result);
25916
+ },
25834
25917
  async send(params) {
25835
25918
  needRoom();
25836
25919
  const result = await inRoom(() => ops("send", { id: newId("m"), to: params.to, body: params.body, dm: params.dm ?? false, replyTo: params.replyTo }));
@@ -26008,6 +26091,8 @@ var TIMING = {
26008
26091
  wakeRetryDelaysMs: [1e3, 3e3, 9e3],
26009
26092
  requestTimeoutMs: 15e3,
26010
26093
  closeTimeoutMs: 2e3,
26094
+ pingIntervalMs: 3e4,
26095
+ pongTimeoutMs: 1e4,
26011
26096
  loginPollFloorMs: 2e3
26012
26097
  };
26013
26098
  function createBridge(options) {
@@ -26071,7 +26156,9 @@ function createBridge(options) {
26071
26156
  if (reason === "rebound" || reason === "not_in_room") forget();
26072
26157
  },
26073
26158
  reconnectDelaysMs: timing.reconnectDelaysMs,
26074
- closeTimeoutMs: timing.closeTimeoutMs
26159
+ closeTimeoutMs: timing.closeTimeoutMs,
26160
+ pingIntervalMs: timing.pingIntervalMs,
26161
+ pongTimeoutMs: timing.pongTimeoutMs
26075
26162
  });
26076
26163
  link.start();
26077
26164
  }
@@ -26093,6 +26180,11 @@ function createBridge(options) {
26093
26180
  unread.reset();
26094
26181
  openLink();
26095
26182
  },
26183
+ remark(patch) {
26184
+ if (!marker || !conversation) return;
26185
+ marker = { ...marker, ...patch };
26186
+ writeMarker(conversation.host, conversation.hostConversationId, marker);
26187
+ },
26096
26188
  forget,
26097
26189
  signedIn: openLink,
26098
26190
  signedOut: () => link?.stop(),
@@ -26124,7 +26216,15 @@ function createBridge(options) {
26124
26216
  waker.evaluate();
26125
26217
  },
26126
26218
  inactive() {
26127
- return inactive({ loggedIn: !!readCredentials(hub), bound: !!conversation, joined: !!marker, ended, connected: !!link?.connected });
26219
+ return inactive({
26220
+ loggedIn: !!readCredentials(hub),
26221
+ bound: !!conversation,
26222
+ joined: !!marker,
26223
+ ended,
26224
+ connected: !!link?.connected,
26225
+ wakeable: options.shell.wakeable(),
26226
+ unwakeable: options.shell.unwakeable?.() ?? null
26227
+ });
26128
26228
  },
26129
26229
  dispose() {
26130
26230
  disposed = true;
@@ -26183,8 +26283,9 @@ function tandry(pi) {
26183
26283
  host: "pi",
26184
26284
  shell: {
26185
26285
  // Print/JSON runs exit after their prompt instead of waiting for mail.
26186
- wakeable: () => !!context?.model && (context.mode === "tui" || context.mode === "rpc") && !waitingForOwner,
26187
- idle: () => !!context?.isIdle() && !starting,
26286
+ wakeable: () => !!context?.model && (context.mode === "tui" || context.mode === "rpc"),
26287
+ // A blocking UI prompt is the owner's moment: no wake until it closes, as during a turn.
26288
+ idle: () => !!context?.isIdle() && !starting && !waitingForOwner,
26188
26289
  async wake(notice) {
26189
26290
  pi.sendMessage(message(notice), { triggerTurn: true, deliverAs: "followUp" });
26190
26291
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tandryio/pi",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.4",
4
4
  "type": "module",
5
5
  "pi": {
6
6
  "extensions": [