ds-bot 0.1.0 → 0.1.1
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 +3 -1
- package/client.js +513 -71
- package/index.js +3 -1
- package/package.json +4 -1
- package/src/host/channel.js +12 -5
- package/src/host/dsh-update.js +168 -0
- package/src/host/groups.js +2 -0
- package/src/host/models.js +65 -8
- package/src/host/parts.js +18 -2
- package/src/host/permissions.js +42 -0
- package/src/host/roster.js +14 -1
- package/src/host/update.js +45 -40
- package/src/host/usage.js +26 -4
package/src/host/update.js
CHANGED
|
@@ -7,10 +7,10 @@ import { join } from 'node:path'
|
|
|
7
7
|
|
|
8
8
|
const PKG = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'))
|
|
9
9
|
export const OFFICIAL_REGISTRY = 'https://registry.npmjs.org/'
|
|
10
|
-
const MIRROR_REGISTRY = 'https://registry.npmmirror.com/'
|
|
10
|
+
export const MIRROR_REGISTRY = 'https://registry.npmmirror.com/'
|
|
11
11
|
// Every window that opens asks; within this long the last answer stands.
|
|
12
|
-
const CHECK_TTL_MS = 10 * 60_000
|
|
13
|
-
const FETCH_TIMEOUT_MS = 8000
|
|
12
|
+
export const CHECK_TTL_MS = 10 * 60_000
|
|
13
|
+
export const FETCH_TIMEOUT_MS = 8000
|
|
14
14
|
// The first check waits until the host has settled its own start.
|
|
15
15
|
const STARTUP_DELAY_MS = 15_000
|
|
16
16
|
const VERSION = /^v?(\d+\.\d+\.\d+(?:-[\w.]+)?)(?:\+[\w.]+)?$/
|
|
@@ -60,10 +60,47 @@ export function sourceOf(spec) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
const normalizeRegistry = url => `${String(url).trim().replace(/\/+$/, '')}/`
|
|
63
|
-
const encodeName = name => (name.startsWith('@') ? `@${encodeURIComponent(name.slice(1))}` : encodeURIComponent(name))
|
|
64
|
-
|
|
63
|
+
export const encodeName = name => (name.startsWith('@') ? `@${encodeURIComponent(name.slice(1))}` : encodeURIComponent(name))
|
|
64
|
+
|
|
65
|
+
export const fetchJson = async (url, headers = {}) => {
|
|
66
|
+
const response = await fetch(url, { headers: { accept: 'application/json', ...headers }, signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) })
|
|
67
|
+
if (!response.ok) throw new Error(`${url} answered ${response.status}`)
|
|
68
|
+
return response.json()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// The registries the plugin manager installs from, in its order: its configured
|
|
72
|
+
// first one (else pnpm's own), then the fallbacks, unless that first one is private.
|
|
73
|
+
export async function registryPlan(config, manager) {
|
|
74
|
+
if (typeof config.updateRegistry === 'string' && config.updateRegistry !== '') return [normalizeRegistry(config.updateRegistry)]
|
|
75
|
+
const configured = await Promise.resolve(manager?.registries?.()).catch(() => null)
|
|
76
|
+
if (!configured) return [OFFICIAL_REGISTRY, MIRROR_REGISTRY]
|
|
77
|
+
const fallbacks = (configured.fallbackRegistries ?? []).map(normalizeRegistry)
|
|
78
|
+
const own = configured.resolved ? normalizeRegistry(configured.resolved) : null
|
|
79
|
+
const first = configured.registry ? normalizeRegistry(configured.registry) : own ?? OFFICIAL_REGISTRY
|
|
80
|
+
const isPublic = first === OFFICIAL_REGISTRY || fallbacks.includes(first)
|
|
81
|
+
return isPublic ? [...new Set([first, ...fallbacks])] : [first]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// The plugin manager's `source` for this package; DSH releases before it reported
|
|
85
|
+
// one leave the profile's own dependency entry to say the same.
|
|
86
|
+
export async function installSpecOf(ctx) {
|
|
87
|
+
const manager = ctx.get?.('pluginManager')
|
|
88
|
+
const bundles = await Promise.resolve(manager?.listBundles?.()).catch(() => null)
|
|
89
|
+
const own = Array.isArray(bundles) ? bundles.find(bundle => bundle.name === PKG.name) : undefined
|
|
90
|
+
if (typeof own?.source === 'string') return own.source
|
|
91
|
+
const dir = ctx.get?.('profileContext')?.dir
|
|
92
|
+
if (typeof dir !== 'string') return undefined
|
|
93
|
+
try {
|
|
94
|
+
return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')).dependencies?.[PKG.name]
|
|
95
|
+
} catch { return undefined }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// The public repository the releases live in, for tags and the "what's new" link.
|
|
99
|
+
export const repoSlug = () =>
|
|
100
|
+
/github\.com[/:]([\w.-]+\/[\w.-]+?)(?:\.git)?$/i.exec(String(PKG.repository?.url ?? PKG.repository ?? ''))?.[1] ?? null
|
|
101
|
+
|
|
65
102
|
const releasesUrl = () => {
|
|
66
|
-
const repo =
|
|
103
|
+
const repo = repoSlug()
|
|
67
104
|
return repo ? `https://github.com/${repo}/releases` : (PKG.homepage ?? null)
|
|
68
105
|
}
|
|
69
106
|
|
|
@@ -86,27 +123,8 @@ export function install(rt) {
|
|
|
86
123
|
available: status.phase !== 'installed' && target !== null && status.latest !== null && newer(status.latest, status.current),
|
|
87
124
|
})
|
|
88
125
|
|
|
89
|
-
const fetchJson = async (url, headers = {}) => {
|
|
90
|
-
const response = await fetch(url, { headers: { accept: 'application/json', ...headers }, signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) })
|
|
91
|
-
if (!response.ok) throw new Error(`${url} answered ${response.status}`)
|
|
92
|
-
return response.json()
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// The registries the plugin manager installs from, in its order: its configured
|
|
96
|
-
// first one (else pnpm's own), then the fallbacks, unless that first one is private.
|
|
97
|
-
async function registryPlan() {
|
|
98
|
-
if (typeof config.updateRegistry === 'string' && config.updateRegistry !== '') return [normalizeRegistry(config.updateRegistry)]
|
|
99
|
-
const configured = await Promise.resolve(manager()?.registries?.()).catch(() => null)
|
|
100
|
-
if (!configured) return [OFFICIAL_REGISTRY, MIRROR_REGISTRY]
|
|
101
|
-
const fallbacks = (configured.fallbackRegistries ?? []).map(normalizeRegistry)
|
|
102
|
-
const own = configured.resolved ? normalizeRegistry(configured.resolved) : null
|
|
103
|
-
const first = configured.registry ? normalizeRegistry(configured.registry) : own ?? OFFICIAL_REGISTRY
|
|
104
|
-
const isPublic = first === OFFICIAL_REGISTRY || fallbacks.includes(first)
|
|
105
|
-
return isPublic ? [...new Set([first, ...fallbacks])] : [first]
|
|
106
|
-
}
|
|
107
|
-
|
|
108
126
|
async function newestFromRegistry() {
|
|
109
|
-
for (const registry of await registryPlan()) {
|
|
127
|
+
for (const registry of await registryPlan(config, manager())) {
|
|
110
128
|
try {
|
|
111
129
|
const version = cleanVersion((await fetchJson(`${registry}${encodeName(PKG.name)}/latest`)).version)
|
|
112
130
|
if (version) return { version, spec: `${PKG.name}@${version}`, registry }
|
|
@@ -124,21 +142,8 @@ export function install(rt) {
|
|
|
124
142
|
return { version, spec: `github:${source.owner}/${source.repo}#${fragment}` }
|
|
125
143
|
}
|
|
126
144
|
|
|
127
|
-
// The plugin manager's `source` for this package; DSH releases before it reported
|
|
128
|
-
// one leave the profile's own dependency entry to say the same.
|
|
129
|
-
async function installSpec() {
|
|
130
|
-
const bundles = await Promise.resolve(manager()?.listBundles?.()).catch(() => null)
|
|
131
|
-
const own = Array.isArray(bundles) ? bundles.find(bundle => bundle.name === PKG.name) : undefined
|
|
132
|
-
if (typeof own?.source === 'string') return own.source
|
|
133
|
-
const dir = ctx.get?.('profileContext')?.dir
|
|
134
|
-
if (typeof dir !== 'string') return undefined
|
|
135
|
-
try {
|
|
136
|
-
return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')).dependencies?.[PKG.name]
|
|
137
|
-
} catch { return undefined }
|
|
138
|
-
}
|
|
139
|
-
|
|
140
145
|
async function runCheck() {
|
|
141
|
-
const source = sourceOf(await
|
|
146
|
+
const source = sourceOf(await installSpecOf(ctx))
|
|
142
147
|
status.source = source.kind
|
|
143
148
|
let found = null
|
|
144
149
|
try {
|
package/src/host/usage.js
CHANGED
|
@@ -320,7 +320,18 @@ export function install(rt) {
|
|
|
320
320
|
...Object.values(room.segments ?? {}).flat().map(segment => segment.sessionId),
|
|
321
321
|
]))
|
|
322
322
|
|
|
323
|
-
|
|
323
|
+
// DSH reads a subagent Session only through its parent's address; the parent
|
|
324
|
+
// need not exist any more. One with no parent on record cannot be read.
|
|
325
|
+
const addressOf = item => (item.origin !== 'subagent'
|
|
326
|
+
? { kind: 'session', sessionId: item.sessionId }
|
|
327
|
+
: item.parentSessionId === undefined
|
|
328
|
+
? undefined
|
|
329
|
+
: { kind: 'subagent', parentSessionId: item.parentSessionId, childSessionId: item.sessionId, mode: 'unknown' })
|
|
330
|
+
// Sessions that already failed to read, so each one warns once, not every scan.
|
|
331
|
+
const unreadable = new Set()
|
|
332
|
+
|
|
333
|
+
async function scanSession(ledger, address, asOfSeq, sealable, signal) {
|
|
334
|
+
const sessionId = address.kind === 'session' ? address.sessionId : address.childSessionId
|
|
324
335
|
const mark = ledger.mark(sessionId)
|
|
325
336
|
if (mark?.sealed) return
|
|
326
337
|
let through = asOfSeq
|
|
@@ -338,7 +349,7 @@ export function install(rt) {
|
|
|
338
349
|
let beforeSeq
|
|
339
350
|
for (let reached = false; !reached;) {
|
|
340
351
|
const page = await ctx.sessionController.page({
|
|
341
|
-
address
|
|
352
|
+
address,
|
|
342
353
|
throughSeq: through,
|
|
343
354
|
...(beforeSeq === undefined ? {} : { beforeSeq }),
|
|
344
355
|
maxMessages: PAGE_MESSAGES,
|
|
@@ -397,8 +408,19 @@ export function install(rt) {
|
|
|
397
408
|
const targets = new Map()
|
|
398
409
|
for (const item of listed) if (item.blank !== true) targets.set(item.sessionId, item.projections?.asOfSeq)
|
|
399
410
|
for (const sessionId of owners.keys()) if (!targets.has(sessionId)) targets.set(sessionId, undefined)
|
|
400
|
-
|
|
401
|
-
|
|
411
|
+
const addresses = new Map(listed.map(item => [item.sessionId, addressOf(item)]))
|
|
412
|
+
await eachLimited([...targets], SCAN_PARALLEL, async ([sessionId, asOfSeq]) => {
|
|
413
|
+
const address = addresses.has(sessionId) ? addresses.get(sessionId) : { kind: 'session', sessionId }
|
|
414
|
+
if (address === undefined) return
|
|
415
|
+
try {
|
|
416
|
+
await scanSession(ledger, address, asOfSeq, ended.has(sessionId), signal)
|
|
417
|
+
unreadable.delete(sessionId)
|
|
418
|
+
} catch (error) {
|
|
419
|
+
if (unreadable.has(sessionId)) return
|
|
420
|
+
unreadable.add(sessionId)
|
|
421
|
+
rt.warn('ds-bot: usage could not read %s: %s', sessionId, error)
|
|
422
|
+
}
|
|
423
|
+
})
|
|
402
424
|
})().finally(() => {
|
|
403
425
|
scannedAt = Date.now()
|
|
404
426
|
scanning = undefined
|