pi-roundtable-web 0.27.0 → 0.28.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 +37 -0
- package/README.md +23 -7
- package/dist/chunk-account-678dgbww.js +1 -0
- package/dist/chunk-action-menu-qpp9bdxh.js +1 -0
- package/dist/chunk-agent-x0yvvdq2.js +1 -0
- package/dist/chunk-arranged-rooms-6rdkk8t1.js +1 -0
- package/dist/chunk-bulk-actions-zbwdaxtc.js +1 -0
- package/dist/chunk-character-creator-215psy17.js +1 -0
- package/dist/chunk-codeeditor-jgj32247.js +2 -0
- package/dist/chunk-confirm-2pzrt272.js +1 -0
- package/dist/chunk-connectors-zha2d834.js +37 -0
- package/dist/chunk-conversations-fb071rez.js +1 -0
- package/dist/chunk-council-sheet-k5fycefd.js +2 -0
- package/dist/chunk-drag-04qfef58.js +1 -0
- package/dist/chunk-entry-mws704mc.js +1 -0
- package/dist/chunk-folder-list-qxhmczwv.js +1 -0
- package/dist/chunk-folds-b7asegfs.js +1 -0
- package/dist/chunk-gate-vxetvn8t.js +1 -0
- package/dist/chunk-group-scene-63yhh1mh.js +1 -0
- package/dist/chunk-group-table-zy4hmgk3.js +1 -0
- package/dist/chunk-host-icons-tyktazzz.js +1 -0
- package/dist/chunk-host-picture-ebhejn2k.js +1 -0
- package/dist/chunk-host-state-wajep0yr.js +1 -0
- package/dist/chunk-host-ui-pc9r24e3.js +1 -0
- package/dist/chunk-index-1af8vsyb.js +2 -0
- package/dist/{chunk-index-g1awv7rk.js → chunk-index-20xgx0k2.js} +2 -2
- package/dist/chunk-index-254701sm.js +3 -0
- package/dist/{chunk-index-ffbrm6tr.js → chunk-index-2822z600.js} +3 -3
- package/dist/{chunk-index-2fdns8tn.js → chunk-index-2hvn2kte.js} +2 -2
- package/dist/chunk-index-2syyyjnq.js +2 -0
- package/dist/chunk-index-2tr41qht.js +2 -0
- package/dist/chunk-index-2y8r4s4f.js +2 -0
- package/dist/chunk-index-31m1b2f5.js +2 -0
- package/dist/{chunk-index-ak6ecqy3.js → chunk-index-3gfbhmpb.js} +2 -2
- package/dist/chunk-index-3yy10a5h.js +2 -0
- package/dist/{chunk-index-sm7m4fej.js → chunk-index-4t6vnwap.js} +2 -2
- package/dist/{chunk-index-j4x9dq30.css → chunk-index-5hbq6g2e.css} +1 -1
- package/dist/chunk-index-5t2jkecy.js +2 -0
- package/dist/chunk-index-5vatmakn.js +2 -0
- package/dist/{chunk-index-zyvwhxga.js → chunk-index-6cg8xp1c.js} +1 -1
- package/dist/chunk-index-6yra9kdq.js +2 -0
- package/dist/{chunk-index-ac1ktr56.js → chunk-index-7qd604rq.js} +2 -2
- package/dist/chunk-index-9bdstf8t.js +2 -0
- package/dist/chunk-index-acj4xxgq.js +100 -0
- package/dist/chunk-index-aexy023z.js +2 -0
- package/dist/chunk-index-amxa2bn3.js +2 -0
- package/dist/chunk-index-bef0fc2m.js +2 -0
- package/dist/chunk-index-c6tphrjr.js +1 -0
- package/dist/chunk-index-c7dbvkrq.js +2 -0
- package/dist/chunk-index-cn49mawx.js +50 -0
- package/dist/{chunk-index-3d8s3565.js → chunk-index-e2gy763y.js} +2 -2
- package/dist/{chunk-index-31x6krbs.js → chunk-index-e35v554x.js} +3 -3
- package/dist/chunk-index-fepsbhr6.js +47 -0
- package/dist/chunk-index-gcmmwh8t.js +2 -0
- package/dist/{chunk-index-kbrg32sx.js → chunk-index-gwkhs8s3.js} +2 -2
- package/dist/chunk-index-h2vhv4kh.js +2 -0
- package/dist/chunk-index-h9ccz1r3.js +2 -0
- package/dist/{chunk-index-0w4bfjb5.js → chunk-index-jc3r4qam.js} +2 -2
- package/dist/{chunk-index-8d81qy69.js → chunk-index-m81362sc.js} +2 -2
- package/dist/{chunk-index-xq84gg8w.js → chunk-index-n0cmnwd2.js} +2 -2
- package/dist/chunk-index-n0ejy0wd.js +2 -0
- package/dist/chunk-index-nkdg1dfz.js +7 -0
- package/dist/chunk-index-p7jkr7gv.js +14 -0
- package/dist/{chunk-index-e2dqjde8.js → chunk-index-q5fyrtx4.js} +1 -1
- package/dist/{chunk-index-jgzygbt8.js → chunk-index-qxynr1hf.js} +2 -2
- package/dist/chunk-index-r7fdgzy1.js +2 -0
- package/dist/chunk-index-rgf4rvns.js +4 -0
- package/dist/chunk-index-rsd8rftk.js +2 -0
- package/dist/{chunk-index-rh0x9rzf.js → chunk-index-s53bxwkk.js} +2 -2
- package/dist/{chunk-index-xxeb2xp0.js → chunk-index-s9p27f9b.js} +3 -3
- package/dist/chunk-index-sdcpf5dp.js +2 -0
- package/dist/chunk-index-sm7kfb07.js +2 -0
- package/dist/chunk-index-sw9r4aa4.js +2 -0
- package/dist/chunk-index-t5bmsqqd.js +1 -0
- package/dist/chunk-index-v8mma5np.js +2 -0
- package/dist/{chunk-index-q7g62sgm.js → chunk-index-v9gbt5tz.js} +3 -3
- package/dist/chunk-index-vsfsv8hd.js +2 -0
- package/dist/chunk-index-wxft0aqb.js +2 -0
- package/dist/chunk-index-x60n1me6.js +2 -0
- package/dist/chunk-index-zcfr8jdv.js +13 -0
- package/dist/{chunk-index-w42rcbx1.js → chunk-index-zj0gjcvr.js} +2 -2
- package/dist/{chunk-index-2t30xwed.js → chunk-index-zt19fdmc.js} +2 -2
- package/dist/chunk-index-zvvwxkev.js +2 -0
- package/dist/chunk-lang-css-7q1dtpgm.js +1 -0
- package/dist/{chunk-lang-dockerfile-cnwqn660.js → chunk-lang-dockerfile-n4bt8jbt.js} +1 -1
- package/dist/{chunk-lang-go-yy6kgsxv.js → chunk-lang-go-9k4y107e.js} +3 -3
- package/dist/chunk-lang-html-xgvba8rn.js +1 -0
- package/dist/chunk-lang-javascript-vgxmgd0a.js +1 -0
- package/dist/chunk-lang-json-hr12bzs1.js +1 -0
- package/dist/chunk-lang-markdown-ttzcpgkg.js +6 -0
- package/dist/chunk-lang-python-ntj8maem.js +3 -0
- package/dist/chunk-lang-rust-gv8y859d.js +1 -0
- package/dist/{chunk-lang-shell-1rpgtepw.js → chunk-lang-shell-5zzs09b7.js} +1 -1
- package/dist/chunk-lang-sql-6vmze4pg.js +2 -0
- package/dist/{chunk-lang-toml-tbj7467c.js → chunk-lang-toml-tt0d4rw9.js} +1 -1
- package/dist/chunk-lang-typescript-2v652sh5.js +1 -0
- package/dist/chunk-lang-xml-r2sbqnjv.js +1 -0
- package/dist/chunk-lang-yaml-ex31qbph.js +1 -0
- package/dist/chunk-layout-actions-bfqtg71d.js +1 -0
- package/dist/chunk-menu-items-rztgekfj.js +1 -0
- package/dist/chunk-mode-toggle-m5kgb7c7.js +1 -0
- package/dist/chunk-note-row-40ypghn9.js +204 -0
- package/dist/chunk-notes-a8nqffbf.js +1 -0
- package/dist/chunk-notice-bell-rdnfmtf2.js +1 -0
- package/dist/{chunk-notices-fte9f196.js → chunk-notices-tefx93s6.js} +1 -1
- package/dist/chunk-onboarding-wvh0s3vg.js +1 -0
- package/dist/chunk-overview-zrx3bpwf.js +1 -0
- package/dist/chunk-palette-5xtdvy9a.js +1 -0
- package/dist/chunk-placement-items-rgyrktvy.js +1 -0
- package/dist/chunk-profile-tyb48b79.js +1 -0
- package/dist/chunk-prompt-history-wv0vdsjv.js +1 -0
- package/dist/chunk-prompts-75hgb0t0.js +1 -0
- package/dist/chunk-room-confirms-bcvhpcry.js +1 -0
- package/dist/chunk-room-menu-ha1evv71.js +1 -0
- package/dist/chunk-rooms-menu-button-ej3a2hyc.js +1 -0
- package/dist/chunk-schedules-a5pewkhd.js +1 -0
- package/dist/{chunk-seat-menu-r0k0eawr.js → chunk-seat-menu-eh3ymmks.js} +1 -1
- package/dist/chunk-sheet-hosts-d5zbwbbr.js +29 -0
- package/dist/{chunk-shortcut-help-a6pkjcq7.js → chunk-shortcut-help-bh9ntk9s.js} +2 -2
- package/dist/{chunk-shortcuts-cgt87jkz.js → chunk-shortcuts-6em0atk9.js} +1 -1
- package/dist/chunk-skill-editor-e43mwz01.js +8 -0
- package/dist/chunk-skills-vp107wrp.js +1 -0
- package/dist/chunk-start-over-line-xd1v2p0g.js +16 -0
- package/dist/chunk-switcher-list-dtfh87zy.js +1 -0
- package/dist/{chunk-tab-dot-vr11dfrw.js → chunk-tab-dot-qd640mjr.js} +1 -1
- package/dist/chunk-table-svb2xhfd.js +1 -0
- package/dist/chunk-table2d-5nrhfwft.js +1 -0
- package/dist/chunk-table3d-5cb66d99.js +4220 -0
- package/dist/chunk-thread-actions-926eab0w.js +1 -0
- package/dist/{chunk-thread-cards-c2mrp2gj.js → chunk-thread-cards-kgt22sbp.js} +1 -1
- package/dist/chunk-thread-list-j2v1pqkz.js +217 -0
- package/dist/chunk-transcript-a0p3vwh6.js +1 -0
- package/dist/chunk-waiting-cdyz7r1g.js +1 -0
- package/dist/chunk-waiting-chip-58ttx9av.js +28 -0
- package/dist/chunk-where-9jp6g029.js +1 -0
- package/dist/chunk-workspace-d34njy4p.js +578 -0
- package/dist/chunk-zh-tw-y3jb20wc.js +1 -0
- package/dist/index.html +1 -1
- package/dist/sw.js +1 -1
- package/package.json +3 -3
- package/src/api-types.ts +98 -1
- package/src/app/app.ts +4 -7
- package/src/app/desk.ts +19 -4
- package/src/app/room-log.ts +66 -18
- package/src/app/rooms.ts +15 -5
- package/src/app/routes.ts +60 -9
- package/src/app/skill-doc-routes.ts +12 -2
- package/src/app/surface.ts +26 -2
- package/src/app/team-routes.ts +3 -7
- package/src/authz.ts +1 -0
- package/src/cleanup-api.ts +9 -2
- package/src/connector-manage-api.ts +257 -0
- package/src/console-api.ts +60 -2
- package/src/features.ts +91 -0
- package/src/host/reads.ts +10 -1
- package/src/i18n/zh-tw.ts +107 -4
- package/src/web-plugin.ts +7 -1
- package/dist/chunk-account-1ecx5kt7.js +0 -1
- package/dist/chunk-action-menu-zh2n0ahf.js +0 -1
- package/dist/chunk-agent-ynm2tcbx.js +0 -1
- package/dist/chunk-arranged-rooms-qnz8vese.js +0 -1
- package/dist/chunk-bulk-actions-pr0mw0kc.js +0 -1
- package/dist/chunk-character-creator-caj4pmg4.js +0 -1
- package/dist/chunk-codeeditor-dga6s78p.js +0 -2
- package/dist/chunk-confirm-g76v5d9r.js +0 -1
- package/dist/chunk-connectors-j79ze3j1.js +0 -1
- package/dist/chunk-conversations-2farn72g.js +0 -1
- package/dist/chunk-council-sheet-s9sz1bzf.js +0 -2
- package/dist/chunk-drag-45e8222r.js +0 -1
- package/dist/chunk-entry-q11n47h9.js +0 -1
- package/dist/chunk-folder-list-4gcj7g7h.js +0 -1
- package/dist/chunk-folds-ten4ykfw.js +0 -1
- package/dist/chunk-gate-kcv1a4t0.js +0 -1
- package/dist/chunk-group-scene-12ghcvzd.js +0 -1
- package/dist/chunk-group-table-t458kdgn.js +0 -1
- package/dist/chunk-host-icons-h1k9k96a.js +0 -1
- package/dist/chunk-host-picture-3p0wzn3v.js +0 -1
- package/dist/chunk-host-state-7mdve7cb.js +0 -1
- package/dist/chunk-host-ui-bdgwa6sf.js +0 -1
- package/dist/chunk-index-02vfshtt.js +0 -2
- package/dist/chunk-index-0a81da8z.js +0 -2
- package/dist/chunk-index-12zyshae.js +0 -2
- package/dist/chunk-index-18pgxxw3.js +0 -2
- package/dist/chunk-index-2e4am3hw.js +0 -50
- package/dist/chunk-index-5bfqek1d.js +0 -2
- package/dist/chunk-index-64rsz6pf.js +0 -2
- package/dist/chunk-index-6bkcq9ka.js +0 -2
- package/dist/chunk-index-92zgb7my.js +0 -2
- package/dist/chunk-index-aamhjg5n.js +0 -2
- package/dist/chunk-index-b73y2gc0.js +0 -47
- package/dist/chunk-index-b8cpvr7y.js +0 -2
- package/dist/chunk-index-bhqczxr3.js +0 -2
- package/dist/chunk-index-cbqhzf40.js +0 -13
- package/dist/chunk-index-cdctnw71.js +0 -2
- package/dist/chunk-index-dj0tbfyb.js +0 -2
- package/dist/chunk-index-dnjftvtq.js +0 -2
- package/dist/chunk-index-ebg48jgy.js +0 -2
- package/dist/chunk-index-ek5mek4j.js +0 -14
- package/dist/chunk-index-f6q0stqx.js +0 -2
- package/dist/chunk-index-g3wrs02w.js +0 -1
- package/dist/chunk-index-j0m16cj1.js +0 -2
- package/dist/chunk-index-j4kk1m59.js +0 -2
- package/dist/chunk-index-kb3w11mn.js +0 -7
- package/dist/chunk-index-msqhgvfx.js +0 -2
- package/dist/chunk-index-pefwfm26.js +0 -2
- package/dist/chunk-index-pyfx77cj.js +0 -2
- package/dist/chunk-index-qfgkfq86.js +0 -2
- package/dist/chunk-index-sv9qpmhv.js +0 -1
- package/dist/chunk-index-t998yg8y.js +0 -2
- package/dist/chunk-index-tk4se135.js +0 -4
- package/dist/chunk-index-v6smgw77.js +0 -2
- package/dist/chunk-index-x4enmj72.js +0 -2
- package/dist/chunk-lang-css-y9133ktr.js +0 -1
- package/dist/chunk-lang-html-r2xmryek.js +0 -1
- package/dist/chunk-lang-javascript-1r6wd177.js +0 -1
- package/dist/chunk-lang-json-dx56g11y.js +0 -1
- package/dist/chunk-lang-markdown-fpcf2dfq.js +0 -6
- package/dist/chunk-lang-python-zzp95955.js +0 -3
- package/dist/chunk-lang-rust-214fh7sp.js +0 -1
- package/dist/chunk-lang-sql-vvp46hpj.js +0 -2
- package/dist/chunk-lang-typescript-y5w4tt4g.js +0 -1
- package/dist/chunk-lang-xml-tbcqmedk.js +0 -1
- package/dist/chunk-lang-yaml-jh56t3sv.js +0 -1
- package/dist/chunk-layout-actions-y73mpsh1.js +0 -1
- package/dist/chunk-menu-items-bza16bw6.js +0 -2
- package/dist/chunk-mode-toggle-nj9gnt3b.js +0 -1
- package/dist/chunk-note-row-6fmhrdng.js +0 -75
- package/dist/chunk-notes-wv0e6ssf.js +0 -1
- package/dist/chunk-notice-bell-9z27epd2.js +0 -1
- package/dist/chunk-onboarding-c5twahc3.js +0 -1
- package/dist/chunk-overview-k4ccprax.js +0 -1
- package/dist/chunk-palette-9rbp2fab.js +0 -1
- package/dist/chunk-placement-items-me598wsb.js +0 -1
- package/dist/chunk-profile-s7r7fcvz.js +0 -1
- package/dist/chunk-prompt-history-hf296e7v.js +0 -1
- package/dist/chunk-prompts-c8s0nhkm.js +0 -1
- package/dist/chunk-room-confirms-ga2sbvhc.js +0 -1
- package/dist/chunk-room-menu-7h4jym2b.js +0 -1
- package/dist/chunk-rooms-menu-button-pgtaanb6.js +0 -1
- package/dist/chunk-schedules-mbnwcxpc.js +0 -1
- package/dist/chunk-sheet-hosts-a5dfevy9.js +0 -1
- package/dist/chunk-skill-editor-67yk4x7t.js +0 -8
- package/dist/chunk-skills-rewemey4.js +0 -1
- package/dist/chunk-switcher-list-t8rbay2y.js +0 -1
- package/dist/chunk-table-52nh0azk.js +0 -1
- package/dist/chunk-table2d-g57vr6h0.js +0 -1
- package/dist/chunk-table3d-rp0w6kt4.js +0 -4220
- package/dist/chunk-thread-actions-qcxt1y1g.js +0 -1
- package/dist/chunk-thread-list-0071q455.js +0 -217
- package/dist/chunk-transcript-789yqysq.js +0 -1
- package/dist/chunk-where-hmwmme9e.js +0 -1
- package/dist/chunk-workspace-wr1qrtsh.js +0 -578
- package/dist/chunk-zh-tw-z5p5mpph.js +0 -1
package/src/app/room-log.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { SQL } from "bun";
|
|
2
2
|
import type { Migration } from "pi-roundtable";
|
|
3
|
-
import
|
|
4
|
-
HostAuthorKind,
|
|
5
|
-
PictureRef,
|
|
6
|
-
RoomFile,
|
|
7
|
-
|
|
3
|
+
import {
|
|
4
|
+
type HostAuthorKind,
|
|
5
|
+
type PictureRef,
|
|
6
|
+
type RoomFile,
|
|
7
|
+
type RoomIntercom,
|
|
8
|
+
type RoomMessage,
|
|
9
|
+
START_OVER_TEXT,
|
|
8
10
|
} from "../api-types.ts";
|
|
9
11
|
import { storedJson } from "../host/stored-json.ts";
|
|
10
12
|
|
|
@@ -27,6 +29,8 @@ export interface NewRoomMessage {
|
|
|
27
29
|
at?: Date;
|
|
28
30
|
/** An internal note: shown to the room's readers, never handed to the agent. */
|
|
29
31
|
note?: true;
|
|
32
|
+
/** A message between agents, drawn as a small card. */
|
|
33
|
+
intercom?: RoomIntercom;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
interface Row {
|
|
@@ -44,6 +48,7 @@ interface Row {
|
|
|
44
48
|
picture: PictureRef | string | null;
|
|
45
49
|
edited_at: Date | null;
|
|
46
50
|
note: boolean | null;
|
|
51
|
+
intercom?: RoomIntercom | string | null;
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
/** The most messages one page of history holds. */
|
|
@@ -70,6 +75,9 @@ function messageOf(row: Row): RoomMessage {
|
|
|
70
75
|
}),
|
|
71
76
|
...(row.edited_at === null ? {} : { edited: true as const }),
|
|
72
77
|
...(row.note ? { note: true as const } : {}),
|
|
78
|
+
...(row.intercom == null
|
|
79
|
+
? {}
|
|
80
|
+
: { intercom: storedJson(row.intercom, "intercom card") }),
|
|
73
81
|
};
|
|
74
82
|
}
|
|
75
83
|
|
|
@@ -128,6 +136,22 @@ export class RoomLog {
|
|
|
128
136
|
},
|
|
129
137
|
};
|
|
130
138
|
|
|
139
|
+
/** The column that marks a message between agents, with which way it went and the other agent. */
|
|
140
|
+
static readonly intercomMigration: Migration = {
|
|
141
|
+
name: "web-room-messages-intercom",
|
|
142
|
+
up: async (sql) => {
|
|
143
|
+
await sql`ALTER TABLE web_room_messages ADD COLUMN IF NOT EXISTS intercom jsonb`;
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/** Finds a room's start-over lines without reading the whole room. */
|
|
148
|
+
static readonly startOverMigration: Migration = {
|
|
149
|
+
name: "web-room-messages-start-over",
|
|
150
|
+
up: async (sql) => {
|
|
151
|
+
await sql`CREATE INDEX IF NOT EXISTS web_room_messages_start_over ON web_room_messages (room, id) WHERE kind = 'system' AND text = 'Conversation started over'`;
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
|
|
131
155
|
async append(message: NewRoomMessage): Promise<RoomMessage> {
|
|
132
156
|
const [row] = await this.#insert(message, false);
|
|
133
157
|
if (!row) throw new Error("the room log stored nothing");
|
|
@@ -153,24 +177,25 @@ export class RoomLog {
|
|
|
153
177
|
async #insert(message: NewRoomMessage, once: boolean): Promise<Row[]> {
|
|
154
178
|
const files = JSON.stringify(message.files ?? []);
|
|
155
179
|
const picture = message.picture ? JSON.stringify(message.picture) : null;
|
|
180
|
+
const intercom = message.intercom ? JSON.stringify(message.intercom) : null;
|
|
156
181
|
const at = message.at ?? new Date();
|
|
157
182
|
return once
|
|
158
183
|
? await this.#sql`
|
|
159
184
|
INSERT INTO web_room_messages (room, kind, principal_id, agent, author_name, text, files,
|
|
160
|
-
author_kind, author_label, picture, reference, created_at, note)
|
|
185
|
+
author_kind, author_label, picture, reference, created_at, note, intercom)
|
|
161
186
|
VALUES (${message.room}, ${message.kind}, ${message.principalId ?? null},
|
|
162
187
|
${message.agent ?? null}, ${message.author}, ${message.text}, ${files}::jsonb,
|
|
163
188
|
${message.authorKind ?? null}, ${message.authorLabel ?? null}, ${picture}::jsonb,
|
|
164
|
-
${message.reference ?? null}, ${at}, ${message.note === true})
|
|
189
|
+
${message.reference ?? null}, ${at}, ${message.note === true}, ${intercom}::jsonb)
|
|
165
190
|
ON CONFLICT (room, reference) WHERE reference IS NOT NULL DO NOTHING
|
|
166
191
|
RETURNING *`
|
|
167
192
|
: await this.#sql`
|
|
168
193
|
INSERT INTO web_room_messages (room, kind, principal_id, agent, author_name, text, files,
|
|
169
|
-
author_kind, author_label, picture, reference, created_at, note)
|
|
194
|
+
author_kind, author_label, picture, reference, created_at, note, intercom)
|
|
170
195
|
VALUES (${message.room}, ${message.kind}, ${message.principalId ?? null},
|
|
171
196
|
${message.agent ?? null}, ${message.author}, ${message.text}, ${files}::jsonb,
|
|
172
197
|
${message.authorKind ?? null}, ${message.authorLabel ?? null}, ${picture}::jsonb,
|
|
173
|
-
${message.reference ?? null}, ${at}, ${message.note === true})
|
|
198
|
+
${message.reference ?? null}, ${at}, ${message.note === true}, ${intercom}::jsonb)
|
|
174
199
|
RETURNING *`;
|
|
175
200
|
}
|
|
176
201
|
|
|
@@ -241,27 +266,50 @@ export class RoomLog {
|
|
|
241
266
|
return found;
|
|
242
267
|
}
|
|
243
268
|
|
|
244
|
-
/**
|
|
269
|
+
/**
|
|
270
|
+
* The newest `limit` messages older than `before` (all of them when absent), oldest first, of
|
|
271
|
+
* the room's conversation: from its latest start-over line, which the page includes, on. With
|
|
272
|
+
* `until`, the id of one of its start-over lines, those of the conversation that line ended
|
|
273
|
+
* instead: older than the line, back to the start-over line before it.
|
|
274
|
+
*/
|
|
245
275
|
async page(
|
|
246
276
|
room: string,
|
|
247
277
|
before: number | undefined,
|
|
248
278
|
limit: number,
|
|
279
|
+
until?: number,
|
|
249
280
|
): Promise<{ messages: RoomMessage[]; more: boolean }> {
|
|
250
281
|
const count = Math.min(Math.max(Math.trunc(limit), 1), PAGE_LIMIT);
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
282
|
+
const start = await this.startOver(room, until);
|
|
283
|
+
// The live conversation includes its own start line; an archived one ends before the next.
|
|
284
|
+
const above = until === undefined ? (start ?? 1) - 1 : (start ?? 0);
|
|
285
|
+
const below =
|
|
286
|
+
until === undefined
|
|
287
|
+
? before
|
|
288
|
+
: before === undefined
|
|
289
|
+
? until
|
|
290
|
+
: Math.min(before, until);
|
|
291
|
+
const rows: Row[] = await this.#sql`
|
|
292
|
+
SELECT * FROM web_room_messages
|
|
293
|
+
WHERE room = ${room} AND id > ${above}
|
|
294
|
+
AND (${below ?? null}::bigint IS NULL OR id < ${below ?? null}::bigint)
|
|
295
|
+
ORDER BY id DESC LIMIT ${count + 1}`;
|
|
259
296
|
return {
|
|
260
297
|
messages: rows.slice(0, count).map(messageOf).reverse(),
|
|
261
298
|
more: rows.length > count,
|
|
262
299
|
};
|
|
263
300
|
}
|
|
264
301
|
|
|
302
|
+
/** The id of the room's latest start-over line, or of the latest one older than `before`. */
|
|
303
|
+
async startOver(room: string, before?: number): Promise<number | undefined> {
|
|
304
|
+
const [row]: { id: string | number | bigint | null }[] = await this.#sql`
|
|
305
|
+
SELECT max(id) AS id FROM web_room_messages
|
|
306
|
+
WHERE room = ${room} AND kind = 'system' AND text = ${START_OVER_TEXT}
|
|
307
|
+
AND (${before ?? null}::bigint IS NULL OR id < ${before ?? null}::bigint)`;
|
|
308
|
+
return row?.id === null || row?.id === undefined
|
|
309
|
+
? undefined
|
|
310
|
+
: Number(row.id);
|
|
311
|
+
}
|
|
312
|
+
|
|
265
313
|
/** Deletes every message of a room that is gone for good; returns how many. */
|
|
266
314
|
async removeRoom(room: string): Promise<number> {
|
|
267
315
|
const rows: unknown[] = await this
|
package/src/app/rooms.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
AgentTeam,
|
|
6
6
|
AvatarStudio,
|
|
7
7
|
ChannelKey,
|
|
8
|
+
ConversationMode,
|
|
8
9
|
ConversationModes,
|
|
9
10
|
} from "pi-roundtable";
|
|
10
11
|
import {
|
|
@@ -15,7 +16,18 @@ import {
|
|
|
15
16
|
isHelper,
|
|
16
17
|
roomOf,
|
|
17
18
|
} from "pi-roundtable";
|
|
18
|
-
import type { RoomKind, RoomView } from "../api-types.ts";
|
|
19
|
+
import type { RoomKind, RoomMode, RoomView } from "../api-types.ts";
|
|
20
|
+
|
|
21
|
+
/** A conversation's modes as a room shows them: an inherited one names where it comes from. */
|
|
22
|
+
export function roomModeOf(mode: ConversationMode): RoomMode {
|
|
23
|
+
const from = mode.inheritedFrom;
|
|
24
|
+
return {
|
|
25
|
+
yolo: mode.yolo,
|
|
26
|
+
longRunning: mode.longRunning,
|
|
27
|
+
...(from ? { inheritedFrom: from.name ?? from.channel } : {}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
import type { PushMutes } from "../push/mutes.ts";
|
|
20
32
|
import type { RoomLog } from "./room-log.ts";
|
|
21
33
|
import type { WebThreads } from "./threads.ts";
|
|
@@ -176,12 +188,11 @@ export class Rooms {
|
|
|
176
188
|
const thread = await this.#options.threads.get(room.name);
|
|
177
189
|
if (!thread) return undefined;
|
|
178
190
|
const { modes, activity, log } = this.#options;
|
|
179
|
-
const { yolo, longRunning } = modes.peek(key);
|
|
180
191
|
const last = (await log.lastOf([key])).get(key);
|
|
181
192
|
return {
|
|
182
193
|
home: false,
|
|
183
194
|
readOnly: true,
|
|
184
|
-
mode:
|
|
195
|
+
mode: roomModeOf(modes.peek(key)),
|
|
185
196
|
...activity(key),
|
|
186
197
|
kind: "thread",
|
|
187
198
|
name: thread.id,
|
|
@@ -207,11 +218,10 @@ export class Rooms {
|
|
|
207
218
|
key: ChannelKey,
|
|
208
219
|
rest: Pick<RoomView, "kind" | "name" | "displayName"> & Partial<RoomView>,
|
|
209
220
|
): RoomView => {
|
|
210
|
-
const { yolo, longRunning } = modes.peek(key);
|
|
211
221
|
return {
|
|
212
222
|
home: key === home,
|
|
213
223
|
readOnly: false,
|
|
214
|
-
mode:
|
|
224
|
+
mode: roomModeOf(modes.peek(key)),
|
|
215
225
|
...(muted.has(key) ? { muted: true as const } : {}),
|
|
216
226
|
...(notes && rest.kind !== "thread" ? { notes: true as const } : {}),
|
|
217
227
|
...activity(key),
|
package/src/app/routes.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
AgentDirectory,
|
|
3
3
|
AgentTeam,
|
|
4
4
|
AvatarStudio,
|
|
5
|
+
ChannelKey,
|
|
5
6
|
ConversationModes,
|
|
6
7
|
ConversationPort,
|
|
7
8
|
IdentityService,
|
|
@@ -37,6 +38,7 @@ import type {
|
|
|
37
38
|
WorkspaceRootsView,
|
|
38
39
|
WorkspaceSave,
|
|
39
40
|
} from "../api-types.ts";
|
|
41
|
+
import { isStartOver } from "../api-types.ts";
|
|
40
42
|
import type { Authenticated } from "../auth/authenticator.ts";
|
|
41
43
|
import { type ConsolePresentation, message } from "../features.ts";
|
|
42
44
|
import type { Gate } from "../gate.ts";
|
|
@@ -56,7 +58,7 @@ import type { LayoutStore } from "./layout.ts";
|
|
|
56
58
|
import { LayoutRoutes } from "./layout-routes.ts";
|
|
57
59
|
import { PromptApi, type PromptApiOptions } from "./prompt-routes.ts";
|
|
58
60
|
import { PAGE_LIMIT, type RoomLog } from "./room-log.ts";
|
|
59
|
-
import type
|
|
61
|
+
import { type ResolvedRoom, type Rooms, roomModeOf } from "./rooms.ts";
|
|
60
62
|
import { SkillDocApi } from "./skill-doc-routes.ts";
|
|
61
63
|
import type { AppSurface } from "./surface.ts";
|
|
62
64
|
import { TeamApi } from "./team-routes.ts";
|
|
@@ -79,12 +81,12 @@ export interface AppApiOptions {
|
|
|
79
81
|
presentation?: ConsolePresentation;
|
|
80
82
|
surface: AppSurface;
|
|
81
83
|
rooms: Rooms;
|
|
82
|
-
log: Pick<RoomLog, "page" | "lastOf" | "removeRoom">;
|
|
84
|
+
log: Pick<RoomLog, "page" | "lastOf" | "removeRoom" | "get" | "startOver">;
|
|
83
85
|
cards: Pick<CardLog, "during" | "removeRoom">;
|
|
84
86
|
uploads: Uploads;
|
|
85
87
|
/** Where stored files are read from. */
|
|
86
88
|
files: FileDir;
|
|
87
|
-
modes: Pick<ConversationModes, "set" | "peek">;
|
|
89
|
+
modes: Pick<ConversationModes, "set" | "peek" | "forget">;
|
|
88
90
|
/** The rooms whose push the owners muted. */
|
|
89
91
|
mutes: Pick<PushMutes, "set" | "setMany">;
|
|
90
92
|
/** The dispatch threads, which go with the room they hang under. */
|
|
@@ -131,10 +133,14 @@ export interface AppApiOptions {
|
|
|
131
133
|
add: readonly string[],
|
|
132
134
|
remove: readonly string[],
|
|
133
135
|
): Promise<string[]>;
|
|
134
|
-
/** The registry's documents: a skill's SKILL.md read, saved and
|
|
136
|
+
/** The registry's documents: a skill's SKILL.md read, saved, made and deleted, and its versions. */
|
|
135
137
|
documents: Pick<
|
|
136
138
|
SkillRegistry,
|
|
137
|
-
|
|
139
|
+
| "document"
|
|
140
|
+
| "saveDocument"
|
|
141
|
+
| "createDocument"
|
|
142
|
+
| "documentHistory"
|
|
143
|
+
| "delete"
|
|
138
144
|
>;
|
|
139
145
|
};
|
|
140
146
|
/** Present when the host has prompt documents (the prompts plugin): the main prompts the owner edits. */
|
|
@@ -580,17 +586,23 @@ export class AppApi {
|
|
|
580
586
|
const params = new URL(request.url).searchParams;
|
|
581
587
|
const before = params.get("before");
|
|
582
588
|
const limit = params.get("limit");
|
|
589
|
+
const archive = params.get("archive");
|
|
583
590
|
const beforeId = before === null ? undefined : Number(before);
|
|
591
|
+
const archiveId = archive === null ? undefined : Number(archive);
|
|
584
592
|
const count = limit === null ? PAGE_LIMIT : Number(limit);
|
|
585
593
|
if (
|
|
586
594
|
(beforeId !== undefined && !Number.isInteger(beforeId)) ||
|
|
595
|
+
(archiveId !== undefined && !Number.isInteger(archiveId)) ||
|
|
587
596
|
!Number.isInteger(count) ||
|
|
588
597
|
count < 1
|
|
589
598
|
)
|
|
590
|
-
throw bad("before and
|
|
599
|
+
throw bad("before, limit and archive must be whole numbers.");
|
|
600
|
+
if (archiveId !== undefined)
|
|
601
|
+
return json(await this.#archived(room.key, archiveId, beforeId, count));
|
|
591
602
|
const page = await log.page(room.key, beforeId, count);
|
|
592
603
|
// The cards stand among the messages by the time they opened; a page that has older
|
|
593
|
-
// messages leaves the cards before its first one to the older page
|
|
604
|
+
// messages leaves the cards before its first one to the older page, and a conversation
|
|
605
|
+
// started over leaves the archived one's cards to it.
|
|
594
606
|
const first = page.messages[0];
|
|
595
607
|
const { reads, nameOf } = this.#options;
|
|
596
608
|
const self = admission.visitor.principal.id;
|
|
@@ -599,7 +611,9 @@ export class AppApi {
|
|
|
599
611
|
...page,
|
|
600
612
|
messages: withExpiredFiles(page.messages, this.#options.files),
|
|
601
613
|
cards: await cards.during(room.key, {
|
|
602
|
-
...(page.more
|
|
614
|
+
...(first && (page.more || isStartOver(first))
|
|
615
|
+
? { from: first.at }
|
|
616
|
+
: {}),
|
|
603
617
|
...(beforeId === undefined ? {} : { before: beforeId }),
|
|
604
618
|
}),
|
|
605
619
|
...(reads && nameOf
|
|
@@ -624,13 +638,19 @@ export class AppApi {
|
|
|
624
638
|
}
|
|
625
639
|
if (Object.keys(change).length === 0)
|
|
626
640
|
throw bad("Give yolo or longRunning.");
|
|
641
|
+
if (modes.peek(room.key).inheritedFrom)
|
|
642
|
+
throw bad(
|
|
643
|
+
"This room follows the modes of the conversation that made it; switch them there.",
|
|
644
|
+
);
|
|
627
645
|
const mode = await modes.set(room.key, change, owner);
|
|
628
|
-
return json({ mode:
|
|
646
|
+
return json({ mode: roomModeOf(mode) });
|
|
629
647
|
}
|
|
630
648
|
if (action === "start-over" && method === "POST" && parts.length === 4) {
|
|
631
649
|
if (room.readOnly) throw notFound();
|
|
632
650
|
await this.#owner(admission);
|
|
633
651
|
await conversations.startFresh(room.key);
|
|
652
|
+
// The other pages read the room list again: its unread count and last line changed.
|
|
653
|
+
this.#options.changed();
|
|
634
654
|
return json({ ok: true });
|
|
635
655
|
}
|
|
636
656
|
if (
|
|
@@ -653,6 +673,37 @@ export class AppApi {
|
|
|
653
673
|
throw new HttpError(404, "There is no such API.");
|
|
654
674
|
}
|
|
655
675
|
|
|
676
|
+
/**
|
|
677
|
+
* A page of the conversation a start-over line of the room ended, read only: its messages and
|
|
678
|
+
* the cards that opened within it. A line that is not one of the room's start-overs names none.
|
|
679
|
+
*/
|
|
680
|
+
async #archived(
|
|
681
|
+
room: ChannelKey,
|
|
682
|
+
until: number,
|
|
683
|
+
before: number | undefined,
|
|
684
|
+
count: number,
|
|
685
|
+
): Promise<MessagesView> {
|
|
686
|
+
const { log, cards } = this.#options;
|
|
687
|
+
const line = await log.get(room, until);
|
|
688
|
+
if (!line || !isStartOver(line)) throw notFound();
|
|
689
|
+
const page = await log.page(room, before, count, until);
|
|
690
|
+
const first = page.messages[0];
|
|
691
|
+
const previous = page.more ? undefined : await log.startOver(room, until);
|
|
692
|
+
const from = page.more
|
|
693
|
+
? first?.at
|
|
694
|
+
: previous === undefined
|
|
695
|
+
? undefined
|
|
696
|
+
: (await log.get(room, previous))?.at;
|
|
697
|
+
return {
|
|
698
|
+
...page,
|
|
699
|
+
messages: withExpiredFiles(page.messages, this.#options.files),
|
|
700
|
+
cards: await cards.during(room, {
|
|
701
|
+
...(from === undefined ? {} : { from }),
|
|
702
|
+
before: before ?? until,
|
|
703
|
+
}),
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
|
|
656
707
|
/**
|
|
657
708
|
* Reads the person's rooms up to their newest message in one call: the named ones, or every room
|
|
658
709
|
* the list shows. A room they may not read is passed over as if it were not named. With `restore`
|
|
@@ -2,6 +2,7 @@ import type { SkillDocument, SkillRegistry } from "pi-roundtable";
|
|
|
2
2
|
import { SkillConflictError } from "pi-roundtable";
|
|
3
3
|
import type {
|
|
4
4
|
SkillConflict,
|
|
5
|
+
SkillDeleted,
|
|
5
6
|
SkillDocCreate,
|
|
6
7
|
SkillDocSave,
|
|
7
8
|
SkillDocView,
|
|
@@ -18,7 +19,11 @@ const MAX_SKILL_BYTES = 64 * 1024;
|
|
|
18
19
|
export interface SkillDocApiOptions {
|
|
19
20
|
skills: Pick<
|
|
20
21
|
SkillRegistry,
|
|
21
|
-
|
|
22
|
+
| "document"
|
|
23
|
+
| "saveDocument"
|
|
24
|
+
| "createDocument"
|
|
25
|
+
| "documentHistory"
|
|
26
|
+
| "delete"
|
|
22
27
|
>;
|
|
23
28
|
/** The catalog the page lists, so a name it does not list is a 404 before the registry is asked. */
|
|
24
29
|
catalog(): SkillView[];
|
|
@@ -34,7 +39,7 @@ const viewOf = (document: SkillDocument): SkillDocView => ({
|
|
|
34
39
|
|
|
35
40
|
/**
|
|
36
41
|
* The app's REST for a skill's SKILL.md: reading it whole, saving an agent-written skill's, making a
|
|
37
|
-
* skill and listing the versions kept. The frontmatter rules are the skill tools': the registry
|
|
42
|
+
* skill, deleting an agent-written one and listing the versions kept. The frontmatter rules are the skill tools': the registry
|
|
38
43
|
* checks them, and a refusal is a 400 with its words. Every call is an owner's; a save names the
|
|
39
44
|
* hash it was edited from, and a skill that changed meanwhile answers 409 with what is there now.
|
|
40
45
|
*/
|
|
@@ -106,6 +111,11 @@ export class SkillDocApi {
|
|
|
106
111
|
throw error;
|
|
107
112
|
}
|
|
108
113
|
}
|
|
114
|
+
// The registry refuses a skill it did not write, in its own words; deleting detaches it from every carrier.
|
|
115
|
+
if (!action && method === "DELETE")
|
|
116
|
+
return json({
|
|
117
|
+
message: await skills.delete(name),
|
|
118
|
+
} satisfies SkillDeleted);
|
|
109
119
|
if (action === "history" && method === "GET" && parts.length === 4) {
|
|
110
120
|
return json({
|
|
111
121
|
versions: (await skills.documentHistory(name)).map((version) => ({
|
package/src/app/surface.ts
CHANGED
|
@@ -14,7 +14,12 @@ import {
|
|
|
14
14
|
roomOf,
|
|
15
15
|
type TurnProgress,
|
|
16
16
|
} from "pi-roundtable";
|
|
17
|
-
import
|
|
17
|
+
import {
|
|
18
|
+
type RoomFile,
|
|
19
|
+
type RoomMessage,
|
|
20
|
+
type RoundView,
|
|
21
|
+
START_OVER_TEXT,
|
|
22
|
+
} from "../api-types.ts";
|
|
18
23
|
import type { OwnerDesk } from "./desk.ts";
|
|
19
24
|
import { type FileBinding, type FileDir, typeByName } from "./files.ts";
|
|
20
25
|
import type { NewRoomMessage, RoomLog } from "./room-log.ts";
|
|
@@ -314,6 +319,23 @@ export class AppSurface implements ChatSurface {
|
|
|
314
319
|
}
|
|
315
320
|
|
|
316
321
|
async sendReply(channel: ChannelKey, reply: OutboundReply): Promise<void> {
|
|
322
|
+
// A message between agents is a card beside the room's turn: it neither ends the turn nor
|
|
323
|
+
// names who speaks in it, and it notifies nobody.
|
|
324
|
+
if (reply.intercom) {
|
|
325
|
+
const { kind, peer, text } = reply.intercom;
|
|
326
|
+
await this.publish({
|
|
327
|
+
room: channel,
|
|
328
|
+
kind: "agent",
|
|
329
|
+
...(reply.author?.agent ? { agent: reply.author.agent } : {}),
|
|
330
|
+
author: reply.author?.name ?? ASSISTANT,
|
|
331
|
+
text,
|
|
332
|
+
intercom: {
|
|
333
|
+
kind,
|
|
334
|
+
peer: { name: peer.name, displayName: peer.displayName },
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
317
339
|
this.#speaks(channel, reply.author?.agent);
|
|
318
340
|
const entry = this.#activity.get(channel);
|
|
319
341
|
if (entry) entry.outcome = reply.failed ? "failed" : "done";
|
|
@@ -357,7 +379,7 @@ export class AppSurface implements ChatSurface {
|
|
|
357
379
|
room: channel,
|
|
358
380
|
kind: "system",
|
|
359
381
|
author: ASSISTANT,
|
|
360
|
-
text:
|
|
382
|
+
text: START_OVER_TEXT,
|
|
361
383
|
});
|
|
362
384
|
}
|
|
363
385
|
|
|
@@ -367,6 +389,8 @@ export class AppSurface implements ChatSurface {
|
|
|
367
389
|
|
|
368
390
|
interim(channel: ChannelKey, as?: PostAuthor): InterimPosts {
|
|
369
391
|
return {
|
|
392
|
+
// The room's progress pane shows the tools a turn calls, so its live text leaves them out.
|
|
393
|
+
tools: false,
|
|
370
394
|
post: async (text) => {
|
|
371
395
|
this.#speaks(channel, as?.agent);
|
|
372
396
|
const live = crypto.randomUUID();
|
package/src/app/team-routes.ts
CHANGED
|
@@ -76,7 +76,7 @@ export interface TeamApiOptions {
|
|
|
76
76
|
log: Pick<RoomLog, "removeRoom">;
|
|
77
77
|
cards: Pick<CardLog, "removeRoom">;
|
|
78
78
|
mutes: Pick<PushMutes, "set">;
|
|
79
|
-
modes: Pick<ConversationModes, "
|
|
79
|
+
modes: Pick<ConversationModes, "forget">;
|
|
80
80
|
/** The dispatch threads under a room, which go with it. */
|
|
81
81
|
threads: Pick<WebThreads, "under" | "removeUnder">;
|
|
82
82
|
/** The owners' shared arrangement, which drops a deleted room's pin and folder place. */
|
|
@@ -467,11 +467,7 @@ export class TeamApi {
|
|
|
467
467
|
await cards.removeRoom(room);
|
|
468
468
|
await mutes.set(room, false, by.principalId);
|
|
469
469
|
await layout.forget(room);
|
|
470
|
-
// A name made again later starts with the modes of a new room.
|
|
471
|
-
await modes.
|
|
472
|
-
room as ChannelKey,
|
|
473
|
-
{ yolo: false, longRunning: false },
|
|
474
|
-
by,
|
|
475
|
-
);
|
|
470
|
+
// A name made again later starts with the modes of a new room, and follows nothing.
|
|
471
|
+
await modes.forget(room as ChannelKey);
|
|
476
472
|
}
|
|
477
473
|
}
|
package/src/authz.ts
CHANGED
|
@@ -42,6 +42,7 @@ export const ROUTES: readonly Rule[] = [
|
|
|
42
42
|
row("*", "api/skills", "O"),
|
|
43
43
|
row("*", "api/skills/:name", "O"),
|
|
44
44
|
row("*", "api/connectors", "O"),
|
|
45
|
+
row("*", "api/connectors/**", "O"),
|
|
45
46
|
row("*", "api/models", "O"),
|
|
46
47
|
// The owner's conversations; a member's own are filtered inside the handler.
|
|
47
48
|
row("*", "api/conversations", "V"),
|
package/src/cleanup-api.ts
CHANGED
|
@@ -22,10 +22,16 @@ export async function cleanupConversation(
|
|
|
22
22
|
const cleanup = features?.cleanup;
|
|
23
23
|
const key = raw as ChannelKey;
|
|
24
24
|
const parsed = parseKey(raw);
|
|
25
|
-
|
|
26
|
-
throw new HttpError(404, "There is no such conversation.");
|
|
25
|
+
// An agent's or group's room in the web app is named by its room key, which no session directory has.
|
|
27
26
|
const owned =
|
|
28
27
|
Boolean(team?.owns(key)) || Boolean(features?.party?.contains(key));
|
|
28
|
+
if (
|
|
29
|
+
!cleanup ||
|
|
30
|
+
(!parsed && !owned) ||
|
|
31
|
+
parsed?.kind === "group" ||
|
|
32
|
+
exclude?.(key)
|
|
33
|
+
)
|
|
34
|
+
throw new HttpError(404, "There is no such conversation.");
|
|
29
35
|
const group =
|
|
30
36
|
(await team?.status())?.groups.some(
|
|
31
37
|
(g) => g.channelId !== undefined && `discord:${g.channelId}` === key,
|
|
@@ -33,6 +39,7 @@ export async function cleanupConversation(
|
|
|
33
39
|
const stored =
|
|
34
40
|
!owned &&
|
|
35
41
|
!group &&
|
|
42
|
+
parsed !== undefined &&
|
|
36
43
|
Boolean(conversationFiles(join(sessionsDir, parsed.dir)));
|
|
37
44
|
if (action === "delete") {
|
|
38
45
|
if (!stored)
|