@waterplus-ai/waterbuddy 0.1.8 → 0.1.10

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,14 +1,14 @@
1
1
  # WaterBuddy
2
2
 
3
- WaterBuddy is a WaterPlus AI integration bundle for DeepSeek Harness. It adds a branded Web
3
+ WaterBuddy is a WaterPlus integration bundle for DeepSeek Harness. It adds a branded Web
4
4
  profile for internal water-industry work while keeping the official Harness base and Web bundles
5
5
  unchanged.
6
6
 
7
7
  ## Features
8
8
 
9
- - WaterPlus AI / WaterBuddy branding, favicon, and page title.
9
+ - WaterBuddy branding, favicon, and page title.
10
10
  - Water-industry assistant persona for internal users.
11
- - In-page WaterPlus AI SSO QR-code login.
11
+ - In-page WaterBuddy SSO QR-code login.
12
12
  - User profile and avatar display after login.
13
13
  - Xiaojia MCP access with the current SSO token attached by a local proxy.
14
14
 
@@ -7,20 +7,30 @@ window.__ModuleLoader__.load({
7
7
  const mark = 'data:image/png;base64,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'
8
8
  const wordmark = 'data:image/png;base64,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'
9
9
 
10
- function WaterPlusMark({ size, className }) {
10
+ function WaterBuddyMark({ size, className }) {
11
11
  return jsx('img', {
12
12
  src: mark,
13
13
  width: size,
14
14
  height: size,
15
15
  className,
16
- alt: 'WaterPlus AI',
16
+ alt: 'WaterBuddy',
17
17
  })
18
18
  }
19
19
 
20
- function WaterPlusName() {
20
+ function WaterBuddyName() {
21
21
  return jsxs(Fragment, {
22
22
  children: [
23
- jsx('img', { src: wordmark, width: 99, height: 24, alt: 'WaterPlus AI' }),
23
+ jsx('strong', {
24
+ style: {
25
+ color: '#1677d2',
26
+ fontSize: 18,
27
+ fontWeight: 700,
28
+ letterSpacing: '-.02em',
29
+ lineHeight: '24px',
30
+ whiteSpace: 'nowrap',
31
+ },
32
+ children: 'WaterBuddy',
33
+ }),
24
34
  jsx('small', {
25
35
  style: {
26
36
  color: 'var(--dsw-text-tertiary, #7b8494)',
@@ -42,7 +52,7 @@ window.__ModuleLoader__.load({
42
52
  }
43
53
 
44
54
  function userName(user) {
45
- return user?.name || user?.nickname || user?.username || '小加用户'
55
+ return user?.name || user?.nickname || user?.username || 'WaterBuddy 用户'
46
56
  }
47
57
 
48
58
  function UserAvatar({ user, size = 34 }) {
@@ -124,11 +134,11 @@ window.__ModuleLoader__.load({
124
134
  jsx('button', {
125
135
  type: 'button',
126
136
  onClick: openPanel,
127
- title: user ? name : '登录小加',
137
+ title: user ? name : '登录 WaterBuddy',
128
138
  style: { display: 'flex', alignItems: 'center', gap: 8, width: wide ? 'auto' : 40, maxWidth: wide ? 150 : 40, minHeight: 38, padding: wide ? '5px 9px 5px 6px' : 3, border: user ? '1px solid #e1eafa' : '1px solid transparent', borderRadius: 12, background: user ? '#f5f8ff' : 'transparent', color: 'var(--dsw-text-secondary, #586174)', cursor: 'pointer', textAlign: 'left', transition: 'background .15s ease, border-color .15s ease' },
129
139
  children: [user ? jsx(UserAvatar, { user, size: 28 }) : jsx('img', { src: SSO.image, alt: SSO.title, style: { width: 28, height: 28, borderRadius: 8, objectFit: 'contain', padding: 2, boxSizing: 'border-box', flex: '0 0 auto', background: '#fff', border: '1px solid #dce9fb' } }), wide ? jsxs('span', { style: { minWidth: 0, display: 'grid', gap: 1 }, children: [jsx('strong', { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontSize: 12.5, lineHeight: '16px', color: 'var(--dsw-text-primary, #1f2937)' }, children: user ? name : SSO.title }), jsx('small', { style: { color: '#8a94a6', fontSize: 10.5, lineHeight: '14px' }, children: user ? '水务之窗' : '扫码登录' })] }) : null],
130
140
  }),
131
- open ? jsx('div', { style: { position: 'fixed', inset: 0, zIndex: 1000, background: 'rgba(15, 23, 42, .42)', backdropFilter: 'blur(5px)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16 }, onClick: event => { if (event.target === event.currentTarget) setOpen(false) }, children: jsx('div', { style: { width: 420, maxWidth: 'calc(100vw - 32px)', borderRadius: 22, background: '#fff', overflow: 'hidden', boxShadow: '0 24px 80px rgba(15,23,42,.28)' }, children: user ? jsxs(Fragment, { children: [jsx('div', { style: { position: 'relative', padding: '28px 28px 44px', textAlign: 'center', color: '#fff', background: 'linear-gradient(135deg, #0f6fd6 0%, #1d9bf0 58%, #38bdf8 100%)' }, children: [jsx('button', { type: 'button', onClick: () => setOpen(false), 'aria-label': '关闭', style: { position: 'absolute', top: 14, right: 16, width: 30, height: 30, border: 0, borderRadius: '50%', background: 'rgba(255,255,255,.18)', color: '#fff', fontSize: 20, lineHeight: 1, cursor: 'pointer' }, children: '×' }), jsx('div', { style: { fontSize: 12, opacity: .82, letterSpacing: 1 }, children: 'WATERPLUS AI' }), jsx('div', { style: { marginTop: 8, fontSize: 15, fontWeight: 600 }, children: 'WaterBuddy' }), jsx('div', { style: { position: 'absolute', left: '50%', bottom: -35, transform: 'translateX(-50%)', padding: 5, borderRadius: '50%', background: '#fff', boxShadow: '0 6px 18px rgba(15,23,42,.18)' }, children: jsx(UserAvatar, { user, size: 62 }) })] }), jsxs('div', { style: { padding: '48px 28px 26px', textAlign: 'center' }, children: [jsx('h3', { style: { margin: 0, color: '#162033', fontSize: 22 }, children: name }), jsx('p', { style: { margin: '7px 0 18px', color: '#7b8799', fontSize: 13 }, children: user.organization || user.department || '水务之窗' }), jsxs('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, textAlign: 'left' }, children: [jsxs('div', { style: { padding: '12px 14px', borderRadius: 12, background: '#f5f8fc' }, children: [jsx('small', { style: { color: '#8a94a6', fontSize: 11 }, children: '账号' }), jsx('div', { style: { marginTop: 4, color: '#334155', fontSize: 13, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: account })] }), jsxs('div', { style: { padding: '12px 14px', borderRadius: 12, background: '#f5f8fc' }, children: [jsx('small', { style: { color: '#8a94a6', fontSize: 11 }, children: '身份' }), jsx('div', { style: { marginTop: 4, color: '#334155', fontSize: 13, fontWeight: 600 }, children: isAdmin ? '管理员' : '内部成员' })] })] }), jsx('button', { type: 'button', onClick: logout, style: { ...buttonStyle, width: '100%', marginTop: 18, border: '1px solid #d9e1ec', background: '#fff', color: '#475569' }, children: '退出登录' })] })] }) : jsxs('div', { style: { padding: 28, textAlign: 'center' }, children: [jsx('button', { type: 'button', onClick: () => setOpen(false), 'aria-label': '关闭', style: { display: 'block', marginLeft: 'auto', width: 30, height: 30, border: 0, borderRadius: '50%', background: '#f1f5f9', color: '#64748b', fontSize: 20, lineHeight: 1, cursor: 'pointer' }, children: '×' }), jsx('img', { src: SSO.image, alt: SSO.title, style: { display: 'block', width: 52, height: 52, objectFit: 'contain', margin: '0 auto 12px' } }), jsx('h3', { style: { margin: '14px 0 6px', color: '#162033', fontSize: 21 }, children: 'WaterBuddy' }), jsx('p', { style: { margin: 0, color: '#7b8799', fontSize: 13 }, children: `使用${SSO.title}扫码,安全登录 WaterBuddy` }), qr?.qrCode ? jsx('img', { src: qr.qrCode, alt: '水务之窗登录二维码', style: { display: 'block', width: 220, height: 220, boxSizing: 'border-box', objectFit: 'contain', margin: '20px auto 12px', padding: 10, border: '1px solid #e5ebf3', borderRadius: 16, background: '#fff' } }) : jsx('div', { style: { width: 220, height: 220, boxSizing: 'border-box', margin: '20px auto 12px', display: 'grid', placeItems: 'center', border: '1px solid #e5ebf3', borderRadius: 16, background: '#f8fafc', color: '#7b8799', fontSize: 13 }, children: loading ? '二维码加载中…' : '二维码暂不可用' }), jsx('div', { style: { minHeight: 24, color: error ? '#dc2626' : status === 'SUCCESS' ? '#16a34a' : '#64748b', fontSize: 13 }, children: error || statusText || '请扫码登录' }), jsxs('div', { style: { display: 'flex', gap: 10, justifyContent: 'center', marginTop: 18 }, children: [jsx('button', { type: 'button', onClick: startLogin, disabled: loading, style: { ...buttonStyle, border: 0, background: '#1677d2', color: '#fff', opacity: loading ? .65 : 1 }, children: '刷新二维码' }), jsx('button', { type: 'button', onClick: () => setOpen(false), style: { ...buttonStyle, border: '1px solid #d9e1ec', background: '#fff', color: '#475569' }, children: '关闭' })] })] }) }) }) : null,
141
+ open ? jsx('div', { style: { position: 'fixed', inset: 0, zIndex: 1000, background: 'rgba(15, 23, 42, .42)', backdropFilter: 'blur(5px)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16 }, onClick: event => { if (event.target === event.currentTarget) setOpen(false) }, children: jsx('div', { style: { width: 420, maxWidth: 'calc(100vw - 32px)', borderRadius: 22, background: '#fff', overflow: 'hidden', boxShadow: '0 24px 80px rgba(15,23,42,.28)' }, children: user ? jsxs(Fragment, { children: [jsx('div', { style: { position: 'relative', padding: '28px 28px 44px', textAlign: 'center', color: '#fff', background: 'linear-gradient(135deg, #0f6fd6 0%, #1d9bf0 58%, #38bdf8 100%)' }, children: [jsx('button', { type: 'button', onClick: () => setOpen(false), 'aria-label': '关闭', style: { position: 'absolute', top: 14, right: 16, width: 30, height: 30, border: 0, borderRadius: '50%', background: 'rgba(255,255,255,.18)', color: '#fff', fontSize: 20, lineHeight: 1, cursor: 'pointer' }, children: '×' }), jsx('div', { style: { fontSize: 12, opacity: .82, letterSpacing: 1 }, children: 'WATERBUDDY' }), jsx('div', { style: { marginTop: 8, fontSize: 15, fontWeight: 600 }, children: 'WaterBuddy' }), jsx('div', { style: { position: 'absolute', left: '50%', bottom: -35, transform: 'translateX(-50%)', padding: 5, borderRadius: '50%', background: '#fff', boxShadow: '0 6px 18px rgba(15,23,42,.18)' }, children: jsx(UserAvatar, { user, size: 62 }) })] }), jsxs('div', { style: { padding: '48px 28px 26px', textAlign: 'center' }, children: [jsx('h3', { style: { margin: 0, color: '#162033', fontSize: 22 }, children: name }), jsx('p', { style: { margin: '7px 0 18px', color: '#7b8799', fontSize: 13 }, children: user.organization || user.department || '水务之窗' }), jsxs('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, textAlign: 'left' }, children: [jsxs('div', { style: { padding: '12px 14px', borderRadius: 12, background: '#f5f8fc' }, children: [jsx('small', { style: { color: '#8a94a6', fontSize: 11 }, children: '账号' }), jsx('div', { style: { marginTop: 4, color: '#334155', fontSize: 13, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: account })] }), jsxs('div', { style: { padding: '12px 14px', borderRadius: 12, background: '#f5f8fc' }, children: [jsx('small', { style: { color: '#8a94a6', fontSize: 11 }, children: '身份' }), jsx('div', { style: { marginTop: 4, color: '#334155', fontSize: 13, fontWeight: 600 }, children: isAdmin ? '管理员' : '内部成员' })] })] }), jsx('button', { type: 'button', onClick: logout, style: { ...buttonStyle, width: '100%', marginTop: 18, border: '1px solid #d9e1ec', background: '#fff', color: '#475569' }, children: '退出登录' })] })] }) : jsxs('div', { style: { padding: 28, textAlign: 'center' }, children: [jsx('button', { type: 'button', onClick: () => setOpen(false), 'aria-label': '关闭', style: { display: 'block', marginLeft: 'auto', width: 30, height: 30, border: 0, borderRadius: '50%', background: '#f1f5f9', color: '#64748b', fontSize: 20, lineHeight: 1, cursor: 'pointer' }, children: '×' }), jsx('img', { src: SSO.image, alt: SSO.title, style: { display: 'block', width: 52, height: 52, objectFit: 'contain', margin: '0 auto 12px' } }), jsx('h3', { style: { margin: '14px 0 6px', color: '#162033', fontSize: 21 }, children: 'WaterBuddy' }), jsx('p', { style: { margin: 0, color: '#7b8799', fontSize: 13 }, children: `使用${SSO.title}扫码,安全登录 WaterBuddy` }), qr?.qrCode ? jsx('img', { src: qr.qrCode, alt: '水务之窗登录二维码', style: { display: 'block', width: 220, height: 220, boxSizing: 'border-box', objectFit: 'contain', margin: '20px auto 12px', padding: 10, border: '1px solid #e5ebf3', borderRadius: 16, background: '#fff' } }) : jsx('div', { style: { width: 220, height: 220, boxSizing: 'border-box', margin: '20px auto 12px', display: 'grid', placeItems: 'center', border: '1px solid #e5ebf3', borderRadius: 16, background: '#f8fafc', color: '#7b8799', fontSize: 13 }, children: loading ? '二维码加载中…' : '二维码暂不可用' }), jsx('div', { style: { minHeight: 24, color: error ? '#dc2626' : status === 'SUCCESS' ? '#16a34a' : '#64748b', fontSize: 13 }, children: error || statusText || '请扫码登录' }), jsxs('div', { style: { display: 'flex', gap: 10, justifyContent: 'center', marginTop: 18 }, children: [jsx('button', { type: 'button', onClick: startLogin, disabled: loading, style: { ...buttonStyle, border: 0, background: '#1677d2', color: '#fff', opacity: loading ? .65 : 1 }, children: '刷新二维码' }), jsx('button', { type: 'button', onClick: () => setOpen(false), style: { ...buttonStyle, border: '1px solid #d9e1ec', background: '#fff', color: '#475569' }, children: '关闭' })] })] }) }) }) : null,
132
142
  ],
133
143
  })
134
144
  }
@@ -167,10 +177,10 @@ window.__ModuleLoader__.load({
167
177
  }, 'waterbuddy: sidebar footer layout')
168
178
  ctx.slots.inject('sidebar.brand.mark', () =>
169
179
  ctx.slots.inject('sidebar.brand.name', function* () {
170
- yield ctx.slots.register({ name: 'sidebar.brand.mark' }, WaterPlusMark)
171
- yield ctx.slots.register({ name: 'sidebar.brand.name' }, WaterPlusName)
180
+ yield ctx.slots.register({ name: 'sidebar.brand.mark', priority: -10 }, WaterBuddyMark)
181
+ yield ctx.slots.register({ name: 'sidebar.brand.name', priority: -10 }, WaterBuddyName)
172
182
  }))
173
- ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({ name: 'sidebar.footer.action', id: 'waterbuddy-user', order: 90, label: '登录小加' }, UserAction))
183
+ ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({ name: 'sidebar.footer.action', id: 'waterbuddy-user', order: 90, label: '登录 WaterBuddy' }, UserAction))
174
184
  }
175
185
 
176
186
  exports.inject = inject
package/lib/host/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  import https from 'node:https'
2
+ import fs from 'node:fs'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
2
5
  import { WATERPLUS_PERSONA } from '../shared/persona.js'
3
6
 
4
7
  const WATERPLUS_ICON = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64')
@@ -6,8 +9,51 @@ const CLIENT_ID = '2CC7F925-07AE-4AB9-9F17-75B7378D5890'
6
9
  const PLATFORM_URL = 'https://web.shuiwujia.com'
7
10
  const AI_URL = 'https://ai.shuiwujia.com'
8
11
  const sessions = new Map()
12
+ const authFile = path.join(process.env.DSH_HOME || path.join(os.homedir(), '.dsh'), 'waterbuddy-auth.json')
13
+ let persistedAuth
9
14
  let currentUser
10
15
 
16
+ function readPersistedAuth() {
17
+ try {
18
+ const value = JSON.parse(fs.readFileSync(authFile, 'utf8'))
19
+ if (value?.token && value?.user) {
20
+ persistedAuth = value
21
+ currentUser = value.user
22
+ }
23
+ } catch {}
24
+ }
25
+
26
+ function savePersistedAuth(auth) {
27
+ try {
28
+ fs.mkdirSync(path.dirname(authFile), { recursive: true })
29
+ fs.writeFileSync(authFile, JSON.stringify(auth), { mode: 0o600 })
30
+ try { fs.chmodSync(authFile, 0o600) } catch {}
31
+ } catch (error) {
32
+ console.warn('[WaterBuddy] failed to persist login:', error.message)
33
+ }
34
+ }
35
+
36
+ function clearPersistedAuth() {
37
+ persistedAuth = undefined
38
+ try { fs.rmSync(authFile, { force: true }) } catch {}
39
+ }
40
+
41
+ function normalizeAvatar(value) {
42
+ if (typeof value !== 'string' || !value) return undefined
43
+ if (value.startsWith('data:')) return value
44
+ try {
45
+ const url = new URL(value, AI_URL)
46
+ if (url.protocol !== 'https:' || url.host !== new URL(AI_URL).host) return undefined
47
+ return `/api/waterbuddy/avatar?url=${encodeURIComponent(url.href)}`
48
+ } catch { return undefined }
49
+ }
50
+
51
+ function normalizeUser(profile) {
52
+ const user = { ...(profile || {}), provider: '水务之窗' }
53
+ user.avatar = normalizeAvatar(user.avatar || user.avatarUrl || user.headImg || user.headImgUrl)
54
+ return user
55
+ }
56
+
11
57
  const MANIFEST = JSON.stringify({ id: '/', name: 'WaterBuddy', short_name: 'WaterBuddy', start_url: '/', scope: '/', display: 'fullscreen', icons: [{ src: '/waterplus-mark.png', sizes: '84x84', type: 'image/png', purpose: 'any' }] })
12
58
 
13
59
  function json(res, status, body) {
@@ -71,15 +117,17 @@ async function pollLogin(session) {
71
117
  const token = validated.body.result
72
118
  const profile = await requestJson(`${AI_URL}/swj-waterplusai-api/client/user/info`, { headers: { Authorization: `Bearer ${token}` } })
73
119
  session.token = token
74
- session.user = { ...(profile.body.result || {}), provider: '水务之窗' }
120
+ session.user = normalizeUser(profile.body.result)
75
121
  currentUser = session.user
122
+ persistedAuth = { token, user: currentUser }
123
+ savePersistedAuth(persistedAuth)
76
124
  return { status: 'SUCCESS', user: currentUser }
77
125
  }
78
126
 
79
127
  async function proxyMcp(req, res) {
80
128
  const headers = {}
81
129
  for (const name of ['accept', 'content-type', 'mcp-session-id', 'last-event-id']) if (req.headers[name]) headers[name] = req.headers[name]
82
- const token = [...sessions.values()].find(session => session.token)?.token
130
+ const token = [...sessions.values()].find(session => session.token)?.token || persistedAuth?.token
83
131
  if (token) headers.authorization = `Bearer ${token}`
84
132
  const chunks = []
85
133
  for await (const chunk of req) chunks.push(chunk)
@@ -91,9 +139,22 @@ async function proxyMcp(req, res) {
91
139
  res.end()
92
140
  }
93
141
 
142
+ async function proxyAvatar(req, res) {
143
+ const value = new URL(req.url, 'http://127.0.0.1').searchParams.get('url')
144
+ let target
145
+ try { target = new URL(value) } catch { return json(res, 400, { error: 'invalid-avatar-url' }) }
146
+ if (target.protocol !== 'https:' || target.host !== new URL(AI_URL).host) return json(res, 403, { error: 'avatar-host-not-allowed' })
147
+ const response = await fetch(target)
148
+ if (!response.ok || !response.body) return json(res, response.status || 502, { error: 'avatar-fetch-failed' })
149
+ res.writeHead(response.status, { 'content-type': response.headers.get('content-type') || 'image/jpeg', 'cache-control': 'private, max-age=3600' })
150
+ for await (const chunk of response.body) res.write(chunk)
151
+ res.end()
152
+ }
153
+
94
154
  export const inject = ['webServer', 'systemPrompt']
95
155
 
96
156
  export function apply(ctx) {
157
+ readPersistedAuth()
97
158
  ctx.effect(() => {
98
159
  const routes = [
99
160
  { kind: 'exact', path: '/waterplus-mark.png', handler: (_req, res) => { res.writeHead(200, { 'content-type': 'image/png', 'cache-control': 'public, max-age=3600' }); res.end(WATERPLUS_ICON) } },
@@ -101,12 +162,13 @@ export function apply(ctx) {
101
162
  { kind: 'exact', path: '/api/waterbuddy/auth/qr', handler: async (req, res) => { if (req.method !== 'POST') return json(res, 405, { error: 'method-not-allowed' }); try { json(res, 200, await createLogin()) } catch (error) { json(res, 502, { error: error.message }) } } },
102
163
  { kind: 'exact', path: '/api/waterbuddy/auth/status', handler: async (req, res) => { if (req.method !== 'GET') return json(res, 405, { error: 'method-not-allowed' }); const sessionId = new URL(req.url, 'http://127.0.0.1').searchParams.get('sessionId'); const session = sessionId && sessions.get(sessionId); if (!session) return json(res, 404, { error: 'session-not-found' }); try { json(res, 200, await pollLogin(session)) } catch (error) { json(res, 502, { error: error.message }) } } },
103
164
  { kind: 'exact', path: '/api/waterbuddy/user', handler: (_req, res) => json(res, 200, { user: currentUser || null }) },
104
- { kind: 'exact', path: '/api/waterbuddy/logout', handler: (_req, res) => { currentUser = undefined; for (const session of sessions.values()) delete session.token; json(res, 200, { ok: true }) } },
165
+ { kind: 'exact', path: '/api/waterbuddy/avatar', handler: async (req, res) => { try { await proxyAvatar(req, res) } catch (error) { json(res, 502, { error: error.message }) } } },
166
+ { kind: 'exact', path: '/api/waterbuddy/logout', handler: (_req, res) => { currentUser = undefined; for (const session of sessions.values()) delete session.token; clearPersistedAuth(); json(res, 200, { ok: true }) } },
105
167
  { kind: 'prefix', path: '/api/waterbuddy/mcp', handler: async (req, res) => { try { await proxyMcp(req, res) } catch (error) { json(res, 502, { error: error.message }) } } },
106
168
  ]
107
169
  const disposers = routes.map(route => ctx.webServer.register(route))
108
170
  const disposeTitle = ctx.webServer.tapIndex(html => html.replace(/<title>[^<]*<\/title>/i, '<title>WaterBuddy</title>').replace(/<link[^>]+rel=["']icon["'][^>]*>/i, '<link rel="icon" type="image/png" href="/waterplus-mark.png" />'))
109
171
  const disposePersona = ctx.systemPrompt.section({ name: 'waterplus:persona', order: 100, text: WATERPLUS_PERSONA })
110
- return () => { for (const dispose of disposers) dispose(); disposeTitle(); disposePersona(); sessions.clear(); currentUser = undefined }
172
+ return () => { for (const dispose of disposers) dispose(); disposeTitle(); disposePersona(); sessions.clear(); currentUser = undefined; persistedAuth = undefined }
111
173
  }, 'waterbuddy: identity and auth')
112
174
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@waterplus-ai/waterbuddy",
3
- "version": "0.1.8",
4
- "description": "WaterPlus AI WaterBuddy branding and Web profile bundle for DeepSeek Harness",
3
+ "version": "0.1.10",
4
+ "description": "WaterBuddy branding and Web profile bundle for DeepSeek Harness",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "main": "lib/host/index.js",