bothouse-dsh 0.1.59 → 0.1.60
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/collab/dsh-member.mjs +1 -1
- package/collab/hub.mjs +17 -4
- package/collab/page.html +62 -3
- package/collab/plugin.mjs +2 -2
- package/collab/quota-cooling.mjs +4 -4
- package/collab/roster.mjs +3 -2
- package/collab/store.mjs +7 -5
- package/node_modules/bothouse-dsh-channels/client.js +5 -6
- package/package.json +1 -1
package/collab/dsh-member.mjs
CHANGED
|
@@ -597,7 +597,7 @@ export class DshMembers {
|
|
|
597
597
|
const answered = this.approvals.answerByText(msg);
|
|
598
598
|
const hit = mentions(msg.text, this.hub.routableIn(msg.room_id));
|
|
599
599
|
const only = this.hub.replyRoute(msg);
|
|
600
|
-
const leadID = this.hub.
|
|
600
|
+
const leadID = this.hub.defaultReplyIn(msg.room_id)?.id;
|
|
601
601
|
const leadTarget = msg.author_kind === 'user' && leadID !== undefined && target === undefined && only === null && hit.size === 0;
|
|
602
602
|
const visible = new Set(this.hub.routableIn(msg.room_id).map(m => m.id));
|
|
603
603
|
const targets = [...this.config.keys()].filter(id => !this.hub.quota?.cooling(id) && visible.has(id) && id !== msg.author && !answered.has(id) && (!target || target === id)
|
package/collab/hub.mjs
CHANGED
|
@@ -48,9 +48,8 @@ export class Hub {
|
|
|
48
48
|
return member !== undefined && member.kind !== 'human' ? member : undefined;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
/** Channel
|
|
51
|
+
/** Channel primary for delegation; unset channels retain the selected global role. */
|
|
52
52
|
primaryIn(roomId) {
|
|
53
|
-
// #1023: in a private chat the one agent answers every owner message, @ or not.
|
|
54
53
|
const dm = this.store.dmMember?.(roomId);
|
|
55
54
|
if (dm !== undefined) return this.routableIn(roomId).find(m => m.id === dm);
|
|
56
55
|
const id = this.store.leadId(roomId);
|
|
@@ -58,7 +57,18 @@ export class Hub {
|
|
|
58
57
|
return member && this.routableIn(roomId).some(m => m.id === member.id) ? member : undefined;
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
/**
|
|
60
|
+
/** Default replies are channel-local and do not change delegation authority. */
|
|
61
|
+
defaultReplyIn(roomId) {
|
|
62
|
+
// #1023: in a private chat the one agent answers every owner message, @ or not.
|
|
63
|
+
const dm = this.store.dmMember?.(roomId);
|
|
64
|
+
if (dm !== undefined) return this.routableIn(roomId).find(m => m.id === dm);
|
|
65
|
+
const id = this.store.leadId(roomId);
|
|
66
|
+
const members = this.routableIn(roomId);
|
|
67
|
+
if (id === null) return members.length === 1 ? members[0] : undefined;
|
|
68
|
+
return members.find(member => member.id === id);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** UI-only fallback; message routing uses defaultReplyIn(), delegation uses primaryIn(). */
|
|
62
72
|
primaryMember() {
|
|
63
73
|
const fallback = this.fallbackLead === '' ? undefined : this.members.get(this.fallbackLead);
|
|
64
74
|
return this.selectedPrimaryMember() ?? (fallback?.kind !== 'human' ? fallback : undefined);
|
|
@@ -68,6 +78,8 @@ export class Hub {
|
|
|
68
78
|
if (member.avatar?.startsWith('upload:')) member.avatarVersion = Math.random().toString(36).slice(2);
|
|
69
79
|
this.members.set(member.id, member);
|
|
70
80
|
this.emit({ type: 'members', members: this.memberView() });
|
|
81
|
+
// Membership determines an unset channel's effective primary, including its workspaces.
|
|
82
|
+
this.emit({ type: 'rooms', rooms: this.store.roomsView() });
|
|
71
83
|
}
|
|
72
84
|
|
|
73
85
|
removeMember(id) {
|
|
@@ -76,6 +88,7 @@ export class Hub {
|
|
|
76
88
|
for (const room of this.thinking.keys()) this.setThinking(room, id, false);
|
|
77
89
|
for (const room of [...this.queued.keys()]) this.setQueue(room, id, []);
|
|
78
90
|
this.emit({ type: 'members', members: this.memberView() });
|
|
91
|
+
this.emit({ type: 'rooms', rooms: this.store.roomsView() });
|
|
79
92
|
}
|
|
80
93
|
|
|
81
94
|
setOnline(id, online) {
|
|
@@ -283,7 +296,7 @@ export class Hub {
|
|
|
283
296
|
|
|
284
297
|
emit(event) {
|
|
285
298
|
if (event.type === 'rooms') event = { ...event, rooms: event.rooms.map(room => ({ ...room,
|
|
286
|
-
lead_id: this.store.leadId(room.id), effective_lead_id: this.
|
|
299
|
+
lead_id: this.store.leadId(room.id), effective_lead_id: this.defaultReplyIn(room.id)?.id ?? null,
|
|
287
300
|
workspace_path: this.store.workspacePath(room.id, this.workspacePath),
|
|
288
301
|
})) };
|
|
289
302
|
for (const s of this.subscribers) {
|
package/collab/page.html
CHANGED
|
@@ -1026,6 +1026,26 @@ function messageNode(m) {
|
|
|
1026
1026
|
}
|
|
1027
1027
|
card.append(bar);
|
|
1028
1028
|
}
|
|
1029
|
+
// 与原生频道页共用重试接口;后端把已受理的失败卡更新成 retried。
|
|
1030
|
+
if (meta.state === 'failed') {
|
|
1031
|
+
const bar = el('div', 'actions');
|
|
1032
|
+
const retry = el('button', '', '重试');
|
|
1033
|
+
retry.type = 'button';
|
|
1034
|
+
retry.onclick = async () => {
|
|
1035
|
+
retry.disabled = true;
|
|
1036
|
+
retry.textContent = '重试中…';
|
|
1037
|
+
try {
|
|
1038
|
+
await api(`${ROOT}/collab/api/messages/${m.id}/retry`, { method: 'POST' });
|
|
1039
|
+
retry.textContent = '已重试';
|
|
1040
|
+
} catch (err) {
|
|
1041
|
+
retry.disabled = false;
|
|
1042
|
+
retry.textContent = '重试';
|
|
1043
|
+
alert('重试失败:' + err.message);
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
bar.append(retry);
|
|
1047
|
+
card.append(bar);
|
|
1048
|
+
}
|
|
1029
1049
|
col.append(card);
|
|
1030
1050
|
} else if (m.text) col.append(renderBody(m.text, 'body'));
|
|
1031
1051
|
if (Array.isArray(m.attachments) && m.attachments.length) col.append(attachmentList(m.attachments));
|
|
@@ -1151,7 +1171,43 @@ function showState(kind, title, detail, retry) {
|
|
|
1151
1171
|
return box;
|
|
1152
1172
|
}
|
|
1153
1173
|
|
|
1174
|
+
// 来源:openRoom 原有每次读取 100 条;向上分页沿用同一页大小。
|
|
1175
|
+
const HISTORY_LIMIT = 100;
|
|
1176
|
+
let historyPage;
|
|
1177
|
+
|
|
1178
|
+
async function loadOlder() {
|
|
1179
|
+
const page = historyPage;
|
|
1180
|
+
if (!page || page.loading || !page.more) return;
|
|
1181
|
+
page.loading = true;
|
|
1182
|
+
const log = $('log');
|
|
1183
|
+
log.querySelector('.history-error')?.remove();
|
|
1184
|
+
try {
|
|
1185
|
+
const { messages } = await api(`${ROOT}/collab/api/rooms/${page.room}/messages?before=${page.before}&limit=${HISTORY_LIMIT}`);
|
|
1186
|
+
if (historyPage !== page) return;
|
|
1187
|
+
const oldHeight = log.scrollHeight;
|
|
1188
|
+
const oldTop = log.scrollTop;
|
|
1189
|
+
messages.forEach(upsert);
|
|
1190
|
+
if (messages.length) page.before = messages[0].id;
|
|
1191
|
+
page.more = messages.length === HISTORY_LIMIT;
|
|
1192
|
+
log.scrollTop = oldTop + log.scrollHeight - oldHeight;
|
|
1193
|
+
} catch (err) {
|
|
1194
|
+
if (historyPage !== page) return;
|
|
1195
|
+
const retry = el('button', 'btn history-error', '历史加载失败,点击重试');
|
|
1196
|
+
retry.type = 'button';
|
|
1197
|
+
retry.title = err.message;
|
|
1198
|
+
retry.onclick = () => loadOlder();
|
|
1199
|
+
log.prepend(retry);
|
|
1200
|
+
} finally {
|
|
1201
|
+
page.loading = false;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
$('log').addEventListener('scroll', () => {
|
|
1206
|
+
if ($('log').scrollTop === 0) void loadOlder();
|
|
1207
|
+
}, { passive: true });
|
|
1208
|
+
|
|
1154
1209
|
async function openRoom(id) {
|
|
1210
|
+
const page = historyPage = { room: id, before: undefined, more: false, loading: true };
|
|
1155
1211
|
state.room = id; state.synced = 0;
|
|
1156
1212
|
$('log').replaceChildren(showState('loading', '正在加载消息…'));
|
|
1157
1213
|
$('log').setAttribute('aria-busy', 'true');
|
|
@@ -1161,11 +1217,14 @@ async function openRoom(id) {
|
|
|
1161
1217
|
const setting = await api(`${ROOT}/collab/api/rooms/${id}/mode`);
|
|
1162
1218
|
state.mode = setting.dispatch_mode;
|
|
1163
1219
|
renderRooms(); renderMembers();
|
|
1164
|
-
const { messages } = await api(`${ROOT}/collab/api/rooms/${id}/messages?limit
|
|
1165
|
-
if (
|
|
1220
|
+
const { messages } = await api(`${ROOT}/collab/api/rooms/${id}/messages?limit=${HISTORY_LIMIT}`);
|
|
1221
|
+
if (historyPage !== page) return;
|
|
1166
1222
|
const log = $('log');
|
|
1167
1223
|
log.querySelector('.state')?.remove();
|
|
1168
1224
|
messages.forEach(upsert);
|
|
1225
|
+
page.before = messages[0]?.id;
|
|
1226
|
+
page.more = messages.length === HISTORY_LIMIT;
|
|
1227
|
+
page.loading = false;
|
|
1169
1228
|
state.synced = messages.length ? messages[messages.length - 1].id : 0;
|
|
1170
1229
|
if (!log.querySelector('.msg')) log.append(showState('empty', '还没有消息', ''));
|
|
1171
1230
|
$('log').setAttribute('aria-busy', 'false');
|
|
@@ -1173,7 +1232,7 @@ async function openRoom(id) {
|
|
|
1173
1232
|
stuck = true;
|
|
1174
1233
|
$('log').scrollTop = $('log').scrollHeight;
|
|
1175
1234
|
} catch (err) {
|
|
1176
|
-
if (
|
|
1235
|
+
if (historyPage !== page) return;
|
|
1177
1236
|
$('log').replaceChildren(showState('error', '加载失败', err.message, () => openRoom(id)));
|
|
1178
1237
|
$('log').setAttribute('aria-busy', 'false');
|
|
1179
1238
|
}
|
package/collab/plugin.mjs
CHANGED
|
@@ -346,7 +346,7 @@ export async function apply(ctx, rawConfig) {
|
|
|
346
346
|
if (path === '/collab/api/state' && req.method === 'GET') {
|
|
347
347
|
return json(res, 200, { me: OWNER, lead_id: hub.selectedPrimaryMember()?.id ?? null,
|
|
348
348
|
effective_lead_id: hub.primaryMember()?.id ?? null,
|
|
349
|
-
rooms: store.roomsView().map(r => ({ ...r, lead_id: store.leadId(r.id), effective_lead_id: hub.
|
|
349
|
+
rooms: store.roomsView().map(r => ({ ...r, lead_id: store.leadId(r.id), effective_lead_id: hub.defaultReplyIn(r.id)?.id ?? null,
|
|
350
350
|
workspace_path: store.workspacePath(r.id, config.workspacePath) })), members: hub.memberView(),
|
|
351
351
|
thinking: Object.fromEntries([...hub.thinking].map(([k, v]) => [k, [...v]])),
|
|
352
352
|
queued: Object.fromEntries([...hub.queued.keys()].map(r => [r, hub.queueView(r)])),
|
|
@@ -573,7 +573,7 @@ export function roomSettingsRoute(req, res, path, { store, hub, dsh, workspacePa
|
|
|
573
573
|
if (match === null) return undefined;
|
|
574
574
|
const roomId = Number(match[1]);
|
|
575
575
|
if (store.room(roomId) === undefined) return Promise.resolve(json(res, 404, { error: '频道不存在' }));
|
|
576
|
-
const view = () => ({ lead_id: store.leadId(roomId), effective_lead_id: hub.
|
|
576
|
+
const view = () => ({ lead_id: store.leadId(roomId), effective_lead_id: hub.defaultReplyIn(roomId)?.id ?? null,
|
|
577
577
|
workspace_path: store.workspacePath(roomId, workspacePath), max_relay: store.relaySetting(roomId).max_relay });
|
|
578
578
|
if (match[2] === 'renew' && req.method === 'POST') {
|
|
579
579
|
return withBody(req, res, async () => ({ members: await dsh.renewRoom(roomId) }), false);
|
package/collab/quota-cooling.mjs
CHANGED
|
@@ -108,7 +108,7 @@ export class QuotaCooling {
|
|
|
108
108
|
if (!quota.cooling_until && !state.until) continue;
|
|
109
109
|
if (!this.cooling(member.id)) {
|
|
110
110
|
for (const [room, lead] of Object.entries(state.leads ?? {})) {
|
|
111
|
-
if (this.hub.
|
|
111
|
+
if (this.hub.defaultReplyIn(Number(room))?.id === lead.to) {
|
|
112
112
|
this.hub.store.setLead(Number(room), member.id);
|
|
113
113
|
this.notice(Number(room), `${member.id}-${state.until}-restore-${room}`, `${member.name} 额度恢复,主角色已切回。`);
|
|
114
114
|
}
|
|
@@ -124,7 +124,7 @@ export class QuotaCooling {
|
|
|
124
124
|
const key = `${member.id}-${state.until}`;
|
|
125
125
|
const reserved = new Set(Object.values(state.transfers).filter(t => !t.woken).map(t => t.to));
|
|
126
126
|
for (const room of this.hub.store.rooms().filter(r => r.kind !== 'workspace')) {
|
|
127
|
-
if (state.leads[room.id] || this.hub.
|
|
127
|
+
if (state.leads[room.id] || this.hub.defaultReplyIn(room.id)?.id !== member.id) continue;
|
|
128
128
|
const target = this.candidate(room.id, '', member.id, reserved);
|
|
129
129
|
if (!target) continue;
|
|
130
130
|
state.leads[room.id] = { to: target.id, woken: false };
|
|
@@ -135,8 +135,8 @@ export class QuotaCooling {
|
|
|
135
135
|
for (const [room, lead] of Object.entries(state.leads)) {
|
|
136
136
|
if (lead.woken || this.cooling(lead.to)) continue;
|
|
137
137
|
// Replay a crash between persisting intent and changing the selection.
|
|
138
|
-
if (this.hub.
|
|
139
|
-
if (this.hub.
|
|
138
|
+
if (this.hub.defaultReplyIn(Number(room))?.id === member.id) this.hub.store.setLead(Number(room), lead.to);
|
|
139
|
+
if (this.hub.defaultReplyIn(Number(room))?.id !== lead.to) continue;
|
|
140
140
|
const msg = this.notice(Number(room), `${key}-lead-${room}`, `${member.name} 额度冷却,主角色交给 ${this.tasks.nameOf(lead.to)}。请接着推进未完成任务。`);
|
|
141
141
|
if (this.wake(lead.to, msg)) { lead.woken = true; this.save(member.id, state, true); }
|
|
142
142
|
}
|
package/collab/roster.mjs
CHANGED
|
@@ -208,7 +208,7 @@ export class Roster {
|
|
|
208
208
|
this.avatars?.remove(id);
|
|
209
209
|
this.store.removeMember(id);
|
|
210
210
|
// #176: a dead id must not linger as a channel's primary role.
|
|
211
|
-
for (const roomId of this.store.clearLeadOf(id)) this.hub.emit({ type: 'room-settings', room_id: this.store.settingsRoom(roomId), lead_id: null, effective_lead_id: this.hub.
|
|
211
|
+
for (const roomId of this.store.clearLeadOf(id)) this.hub.emit({ type: 'room-settings', room_id: this.store.settingsRoom(roomId), lead_id: null, effective_lead_id: this.hub.defaultReplyIn(roomId)?.id ?? null });
|
|
212
212
|
this.dsh.dropMember(id);
|
|
213
213
|
}
|
|
214
214
|
|
|
@@ -252,7 +252,8 @@ export class Roster {
|
|
|
252
252
|
throw new CollabError('invalid', 'rooms must contain existing chat channel ids');
|
|
253
253
|
}
|
|
254
254
|
const clash = this.hub.memberList().find(m => m.id !== selfId && (!rooms.length || !m.rooms?.length || rooms.some(id => m.rooms.includes(id))) && (m.name.toLowerCase() === name.toLowerCase() || m.id.toLowerCase() === name.toLowerCase()));
|
|
255
|
-
|
|
255
|
+
// #366: 这条会原样显示在成员对话框的错误条里,和同页的成员名校验一样说中文。
|
|
256
|
+
if (clash !== undefined) throw new CollabError('conflict', `名字 ${name} 已被 ${clash.name} 使用`);
|
|
256
257
|
const persona = String(input?.persona ?? '');
|
|
257
258
|
if (persona.length > MAX_PERSONA) throw new CollabError('invalid', `persona exceeds ${MAX_PERSONA} characters`);
|
|
258
259
|
let avatar;
|
package/collab/store.mjs
CHANGED
|
@@ -393,19 +393,21 @@ export class Store {
|
|
|
393
393
|
* workspace is addressed, so a duplicate or a workspace-shaped name is refused with 'conflict'.
|
|
394
394
|
*/
|
|
395
395
|
checkRoomName(name, selfId) {
|
|
396
|
-
|
|
396
|
+
// #366: 这两句会原样显示在客户端改名/新建对话框的错误条里,因此和同页其它校验一样说中文。
|
|
397
|
+
if (new RegExp(`^${WORKSPACE_PREFIX}\\d+$`).test(name)) throw new CollabError('conflict', `${name} 是工作区保留名,换个名字`);
|
|
397
398
|
// A private chat is named after its member and listed with its avatar, so it does not clash.
|
|
398
399
|
const clash = this.rooms().find(r => r.id !== selfId && r.kind === 'chat' && r.dm_member == null && r.name.toLowerCase() === name.toLowerCase());
|
|
399
|
-
if (clash !== undefined) throw new CollabError('conflict',
|
|
400
|
+
if (clash !== undefined) throw new CollabError('conflict', `频道名 ${name} 已被占用`);
|
|
400
401
|
}
|
|
401
402
|
/** Rename the channel without changing any room, message or session identity. */
|
|
402
403
|
renameRoom(id, name) {
|
|
403
404
|
if (typeof name !== 'string' || !name || name.length > 32 || /\s/.test(name)) {
|
|
404
|
-
throw new CollabError('invalid', '
|
|
405
|
+
throw new CollabError('invalid', '频道名 1–32 个字符,且不能有空格');
|
|
405
406
|
}
|
|
406
407
|
const room = this.room(id);
|
|
407
|
-
if (room === undefined) throw new CollabError('room-missing',
|
|
408
|
-
|
|
408
|
+
if (room === undefined) throw new CollabError('room-missing', `频道 ${id} 不存在`);
|
|
409
|
+
// 工作区名是系统分配的(工作区<n>),改名会和一个地址撞名,所以服务端直接拒绝。
|
|
410
|
+
if (room.kind === 'workspace') throw new CollabError('invalid', '工作区名称由系统分配,不能修改');
|
|
409
411
|
this.checkRoomName(name, id);
|
|
410
412
|
return this.db.prepare('UPDATE rooms SET name = ? WHERE id = ? RETURNING id, name, kind, parent_id').get(name, id);
|
|
411
413
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// dsh-client-source-sha256:
|
|
1
|
+
// dsh-client-source-sha256: def898ca7c3b5cbc9806630df8269a00d441300d83397256c948bcbf8313e7f7
|
|
2
2
|
// dsh-style-sha256: f6a6de6373a2ead4c3a965e58c158c16626e49a16b91d2d568b35b19fcfdb95a
|
|
3
3
|
window.__ModuleLoader__.load({
|
|
4
4
|
id: "bothouse-dsh-channels",
|
|
@@ -20541,13 +20541,13 @@ function ChannelSettingsDialog({ room, members, effectiveLead, renameRoom, setDi
|
|
|
20541
20541
|
const [name, setName] = (0, react.useState)(room.name);
|
|
20542
20542
|
const [mode, setMode] = (0, react.useState)(room.dispatch_mode ?? "mixed");
|
|
20543
20543
|
const [relay, setRelay] = (0, react.useState)(String(room.max_relay ?? RELAY_CAP_MIN));
|
|
20544
|
-
const [lead, setLeadDraft] = (0, react.useState)(effectiveLead);
|
|
20544
|
+
const [lead, setLeadDraft] = (0, react.useState)(room.lead_id === void 0 ? effectiveLead : room.lead_id ?? "");
|
|
20545
20545
|
const [directory, setDirectory] = (0, react.useState)(room.workspace_path ?? "");
|
|
20546
20546
|
const [saved, setSaved] = (0, react.useState)({
|
|
20547
20547
|
name: room.name,
|
|
20548
20548
|
mode: room.dispatch_mode ?? "mixed",
|
|
20549
20549
|
relay: String(room.max_relay ?? RELAY_CAP_MIN),
|
|
20550
|
-
lead: effectiveLead,
|
|
20550
|
+
lead: room.lead_id === void 0 ? effectiveLead : room.lead_id ?? "",
|
|
20551
20551
|
directory: room.workspace_path ?? ""
|
|
20552
20552
|
});
|
|
20553
20553
|
const [saving, setSaving] = (0, react.useState)({});
|
|
@@ -20776,7 +20776,6 @@ function ChannelSettingsDialog({ room, members, effectiveLead, renameRoom, setDi
|
|
|
20776
20776
|
},
|
|
20777
20777
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
20778
20778
|
value: "",
|
|
20779
|
-
disabled: true,
|
|
20780
20779
|
children: t("leadNone")
|
|
20781
20780
|
}), members.filter((member) => member.kind !== "human").map((member) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
20782
20781
|
value: member.id,
|
|
@@ -24431,7 +24430,7 @@ const zh = {
|
|
|
24431
24430
|
dispatchTeam: "完全团队 — 交给团队成员协作",
|
|
24432
24431
|
dispatchMixed: "混合 — 按任务选择模型与成员",
|
|
24433
24432
|
lead: "主角色",
|
|
24434
|
-
leadNone: "
|
|
24433
|
+
leadNone: "未选择(单成员自动接话,多成员不接话)",
|
|
24435
24434
|
durSeconds: "{n} 秒",
|
|
24436
24435
|
durMinutes: "{n} 分钟",
|
|
24437
24436
|
durHours: "{h} 小时 {m} 分",
|
|
@@ -24715,7 +24714,7 @@ const en = {
|
|
|
24715
24714
|
taskReviewReason: "Reason for rework (required)",
|
|
24716
24715
|
taskReviewSubmit: "Submit rework",
|
|
24717
24716
|
lead: "Primary role",
|
|
24718
|
-
leadNone: "
|
|
24717
|
+
leadNone: "No selection (one member replies; multiple stay quiet)",
|
|
24719
24718
|
durSeconds: "{n}s",
|
|
24720
24719
|
durMinutes: "{n} min",
|
|
24721
24720
|
durHours: "{h} h {m} min",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bothouse-dsh",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.60",
|
|
4
4
|
"description": "BotHouse channels for DeepSeek Harness: Discord-style rooms where DSH models and local CLIs (Claude Code, Codex, Gemini) are members",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./collab/plugin.mjs",
|