dsh-plugin-weapp-gateway 0.8.23 → 0.8.34
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/LICENSE +21 -21
- package/PROTOCOL.md +1545 -1486
- package/README.md +503 -464
- package/THIRD-PARTY-NOTICES/dsh-cost-meter.txt +31 -31
- package/bin/setup-ip.mjs +474 -474
- package/cordis.patch.yml +50 -50
- package/docs/agent-team-app-integration.md +945 -0
- package/docs/blog-mobile-gateway.md +542 -542
- package/docs/dsh-0.1.5-rc.2-compatibility-audit.md +255 -255
- package/docs/dsh-0.1.6-alpha.1-compatibility-audit.md +122 -122
- package/docs/dsh-0.1.7-rc.1-compatibility-audit.md +52 -52
- package/docs/dsh-0.2.0-rc.2-plugin-management-mobile-integration.md +109 -109
- package/docs/dsh-rc2-mobile-integration.md +155 -155
- package/docs/file-upload-app-integration.md +250 -0
- package/docs/multi-gateway-app-integration.md +141 -125
- package/docs/multi-gateway-phase1-acceptance.md +179 -179
- package/docs/multi-gateway-todo.md +137 -137
- package/docs/remote-gateway-refactor-plan.md +52 -52
- package/docs/runtime-architecture.architecture.json +264 -264
- package/docs/runtime-architecture.html +15001 -15001
- package/docs/runtime-architecture.visual-check.html +32 -32
- package/docs/runtime-architecture.visual-check.json +548 -548
- package/docs/session-agent-preset-app-integration.md +93 -93
- package/docs/turn-outline-app-integration.md +196 -0
- package/docs/typert-remote-gateway-feature-checklist.md +294 -294
- package/docs/voice-input-implementation.md +219 -219
- package/docs/workspace-changes-mobile-integration.md +32 -32
- package/helper/dsh_mobile_gateway_helper.py +227 -227
- package/lib/account-bridge.mjs +57 -57
- package/lib/client.js +1409 -1361
- package/lib/cloudflare-tunnel.mjs +314 -314
- package/lib/cloudflared-binary.mjs +135 -135
- package/lib/context-bridge.mjs +3338 -1715
- package/lib/cost-meter-bridge.mjs +794 -794
- package/lib/cost-meter-dashboard-bridge.mjs +1655 -1449
- package/lib/devices.js +273 -245
- package/lib/dsh-host-adapter.mjs +508 -426
- package/lib/file-upload.mjs +205 -0
- package/lib/gateway-probe.mjs +6 -6
- package/lib/gateway-state.mjs +57 -57
- package/lib/gateway-tools.mjs +36 -36
- package/lib/history-cache.mjs +66 -0
- package/lib/index.mjs +4390 -3803
- package/lib/plugin-management.mjs +251 -233
- package/lib/public-endpoint-check.mjs +61 -61
- package/lib/relay-connector.mjs +213 -181
- package/lib/relay-state.mjs +83 -76
- package/lib/session-follower.mjs +140 -136
- package/lib/session-resume-bridge.mjs +86 -0
- package/lib/session-target.mjs +29 -0
- package/lib/speech-input.mjs +192 -192
- package/lib/vendor/cost-meter/aliyun-balance.mjs +123 -123
- package/lib/vendor/cost-meter/coding-plans.mjs +1668 -1668
- package/lib/vendor/cost-meter/credentials-shim.mjs +8 -8
- package/lib/vendor/cost-meter/custom-balance.mjs +281 -281
- package/lib/vendor/cost-meter/factory-quota.mjs +69 -69
- package/lib/vendor/cost-meter/gateway-quota-adapters.mjs +1006 -1006
- package/lib/vendor/cost-meter/gateway-quotas.mjs +664 -664
- package/lib/vendor/cost-meter/home-paths-shim.mjs +13 -13
- package/lib/vendor/cost-meter/net.mjs +183 -183
- package/lib/vendor/cost-meter/opendesign.mjs +204 -204
- package/lib/vendor/cost-meter/tokenrhythm-balance.mjs +95 -95
- package/lib/vendor/cost-meter/workbuddy-accounts.mjs +44 -44
- package/lib/vendor/cost-meter/workbuddy-transport.mjs +38 -38
- package/lib/vendor/cost-meter/workbuddy.mjs +356 -356
- package/lib/web-restart.mjs +47 -47
- package/lib/wire-json.mjs +7 -7
- package/lib/workspace-changes-bridge.mjs +32 -32
- package/package.json +54 -54
- package/relay/agent-connector.js +588 -569
- package/relay/vendor/tweetnacl.js +2391 -2391
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import { fetchWorkBuddyLocal, workBuddyOrigin } from './workbuddy-transport.mjs'
|
|
2
|
-
import { fetchWithRetry, readJsonBounded } from './net.mjs'
|
|
3
|
-
|
|
4
|
-
export const validWorkBuddyAccountId = id => typeof id === 'string' && /^(cn|global):[^\s\u0000-\u001f\u007f]{1,256}$/.test(id)
|
|
5
|
-
const amount = value => typeof value === 'number' && Number.isFinite(value) && value >= 0
|
|
6
|
-
|
|
7
|
-
/** Read the connector's published per-account endpoint; never switch its active account. */
|
|
8
|
-
export async function fetchWorkBuddyAccounts(hostOrigin, config = {}, { signal, fetchImpl = fetchWorkBuddyLocal } = {}) {
|
|
9
|
-
if (!hostOrigin) return null
|
|
10
|
-
const origin = workBuddyOrigin(hostOrigin)
|
|
11
|
-
const results = await Promise.all(['cn', 'global'].map(async region => {
|
|
12
|
-
try {
|
|
13
|
-
const response = await fetchWithRetry(new URL('/plugins/dsh-connect-workbuddy/account-credits?region=' + region, origin),
|
|
14
|
-
{ method: 'GET', redirect: 'error', signal }, { attempts: 1, timeoutMs: 35000, fetchImpl })
|
|
15
|
-
if (response.status === 404 || response.headers.get('content-type')?.includes('text/html')) { await response.body?.cancel?.(); return { missing: true } }
|
|
16
|
-
if (!response.ok) { await response.body?.cancel?.(); return { failed: true } }
|
|
17
|
-
const body = await readJsonBounded(response)
|
|
18
|
-
if (!Array.isArray(body?.accounts) || body.accounts.length > 200) return { failed: true }
|
|
19
|
-
const seen = new Set(), accounts = []
|
|
20
|
-
for (const row of body.accounts) {
|
|
21
|
-
const id = region + ':' + row?.id
|
|
22
|
-
if (!validWorkBuddyAccountId(id) || typeof row?.id !== 'string' || seen.has(id)) return { failed: true }
|
|
23
|
-
seen.add(id)
|
|
24
|
-
accounts.push({ id, region, name: typeof row.accountName === 'string' ? row.accountName.slice(0, 160) : '',
|
|
25
|
-
current: row.selected === true, remaining: amount(row.credits) ? row.credits : null })
|
|
26
|
-
}
|
|
27
|
-
return { accounts }
|
|
28
|
-
} catch { signal?.throwIfAborted(); return { failed: true } }
|
|
29
|
-
}))
|
|
30
|
-
// An older connector has no account list; its selected-account route remains usable.
|
|
31
|
-
if (!results.some(result => result.accounts)) return null
|
|
32
|
-
const accounts = results.flatMap(result => result.accounts ?? [])
|
|
33
|
-
const scope = ['all', 'selected'].includes(config.accountScope) ? config.accountScope : 'current'
|
|
34
|
-
const ids = new Set(Array.isArray(config.accountIds) ? config.accountIds : [])
|
|
35
|
-
if (scope === 'selected') for (const id of ids) if (validWorkBuddyAccountId(id) && !accounts.some(row => row.id === id)) {
|
|
36
|
-
accounts.push({ id, region: id.split(':')[0], name: '', current: false, remaining: null })
|
|
37
|
-
}
|
|
38
|
-
const selected = accounts.filter(row => scope === 'all' || (scope === 'current' ? row.current : ids.has(row.id)))
|
|
39
|
-
const known = selected.filter(row => row.remaining !== null)
|
|
40
|
-
return { scope, accounts, selectedIds: selected.map(row => row.id),
|
|
41
|
-
knownRemaining: known.reduce((sum, row) => sum + row.remaining, 0),
|
|
42
|
-
unknownCount: selected.length - known.length,
|
|
43
|
-
incomplete: results.some(result => result.failed), fetchedAt: Date.now() }
|
|
44
|
-
}
|
|
1
|
+
import { fetchWorkBuddyLocal, workBuddyOrigin } from './workbuddy-transport.mjs'
|
|
2
|
+
import { fetchWithRetry, readJsonBounded } from './net.mjs'
|
|
3
|
+
|
|
4
|
+
export const validWorkBuddyAccountId = id => typeof id === 'string' && /^(cn|global):[^\s\u0000-\u001f\u007f]{1,256}$/.test(id)
|
|
5
|
+
const amount = value => typeof value === 'number' && Number.isFinite(value) && value >= 0
|
|
6
|
+
|
|
7
|
+
/** Read the connector's published per-account endpoint; never switch its active account. */
|
|
8
|
+
export async function fetchWorkBuddyAccounts(hostOrigin, config = {}, { signal, fetchImpl = fetchWorkBuddyLocal } = {}) {
|
|
9
|
+
if (!hostOrigin) return null
|
|
10
|
+
const origin = workBuddyOrigin(hostOrigin)
|
|
11
|
+
const results = await Promise.all(['cn', 'global'].map(async region => {
|
|
12
|
+
try {
|
|
13
|
+
const response = await fetchWithRetry(new URL('/plugins/dsh-connect-workbuddy/account-credits?region=' + region, origin),
|
|
14
|
+
{ method: 'GET', redirect: 'error', signal }, { attempts: 1, timeoutMs: 35000, fetchImpl })
|
|
15
|
+
if (response.status === 404 || response.headers.get('content-type')?.includes('text/html')) { await response.body?.cancel?.(); return { missing: true } }
|
|
16
|
+
if (!response.ok) { await response.body?.cancel?.(); return { failed: true } }
|
|
17
|
+
const body = await readJsonBounded(response)
|
|
18
|
+
if (!Array.isArray(body?.accounts) || body.accounts.length > 200) return { failed: true }
|
|
19
|
+
const seen = new Set(), accounts = []
|
|
20
|
+
for (const row of body.accounts) {
|
|
21
|
+
const id = region + ':' + row?.id
|
|
22
|
+
if (!validWorkBuddyAccountId(id) || typeof row?.id !== 'string' || seen.has(id)) return { failed: true }
|
|
23
|
+
seen.add(id)
|
|
24
|
+
accounts.push({ id, region, name: typeof row.accountName === 'string' ? row.accountName.slice(0, 160) : '',
|
|
25
|
+
current: row.selected === true, remaining: amount(row.credits) ? row.credits : null })
|
|
26
|
+
}
|
|
27
|
+
return { accounts }
|
|
28
|
+
} catch { signal?.throwIfAborted(); return { failed: true } }
|
|
29
|
+
}))
|
|
30
|
+
// An older connector has no account list; its selected-account route remains usable.
|
|
31
|
+
if (!results.some(result => result.accounts)) return null
|
|
32
|
+
const accounts = results.flatMap(result => result.accounts ?? [])
|
|
33
|
+
const scope = ['all', 'selected'].includes(config.accountScope) ? config.accountScope : 'current'
|
|
34
|
+
const ids = new Set(Array.isArray(config.accountIds) ? config.accountIds : [])
|
|
35
|
+
if (scope === 'selected') for (const id of ids) if (validWorkBuddyAccountId(id) && !accounts.some(row => row.id === id)) {
|
|
36
|
+
accounts.push({ id, region: id.split(':')[0], name: '', current: false, remaining: null })
|
|
37
|
+
}
|
|
38
|
+
const selected = accounts.filter(row => scope === 'all' || (scope === 'current' ? row.current : ids.has(row.id)))
|
|
39
|
+
const known = selected.filter(row => row.remaining !== null)
|
|
40
|
+
return { scope, accounts, selectedIds: selected.map(row => row.id),
|
|
41
|
+
knownRemaining: known.reduce((sum, row) => sum + row.remaining, 0),
|
|
42
|
+
unknownCount: selected.length - known.length,
|
|
43
|
+
incomplete: results.some(result => result.failed), fetchedAt: Date.now() }
|
|
44
|
+
}
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import { request } from 'node:http'
|
|
2
|
-
|
|
3
|
-
export function workBuddyOrigin(value) {
|
|
4
|
-
let url
|
|
5
|
-
try { url = new URL(value) } catch {}
|
|
6
|
-
if (!url || url.protocol !== 'http:' || !['127.0.0.1', '[::1]', 'localhost'].includes(url.hostname)
|
|
7
|
-
|| url.username || url.password || url.pathname !== '/' || url.search || url.hash) {
|
|
8
|
-
throw Object.assign(new Error('workbuddyConnectUnavailable'), { code: 'workbuddyConnectUnavailable' })
|
|
9
|
-
}
|
|
10
|
-
return url
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Host-local reads must not use DSH's global fetch proxy/dispatcher. No credentials,
|
|
14
|
-
// redirects, or configurable remote paths; the byte limit includes the whole body.
|
|
15
|
-
export function fetchWorkBuddyLocal(url, { signal } = {}) {
|
|
16
|
-
workBuddyOrigin(new URL(url).origin)
|
|
17
|
-
return new Promise((resolve, reject) => {
|
|
18
|
-
const req = request(url, { method: 'GET', agent: false, signal, headers: { accept: 'application/json', 'accept-encoding': 'identity' } }, res => {
|
|
19
|
-
const chunks = []; let bytes = 0
|
|
20
|
-
res.on('error', reject)
|
|
21
|
-
res.on('data', chunk => {
|
|
22
|
-
bytes += chunk.length
|
|
23
|
-
if (bytes > 1024 * 1024) { res.destroy(new Error('WorkBuddy response too large')); return }
|
|
24
|
-
chunks.push(chunk)
|
|
25
|
-
})
|
|
26
|
-
res.on('end', () => {
|
|
27
|
-
const status = res.statusCode ?? 502
|
|
28
|
-
if (status >= 300 && status < 400) { reject(new TypeError('fetch failed: redirect refused')); return }
|
|
29
|
-
resolve(new Response([204, 205, 304].includes(status) ? null : Buffer.concat(chunks), {
|
|
30
|
-
status, headers: { 'content-type': res.headers['content-type'] ?? '' },
|
|
31
|
-
}))
|
|
32
|
-
})
|
|
33
|
-
})
|
|
34
|
-
req.on('error', reject)
|
|
35
|
-
req.setTimeout(35000, () => req.destroy(new Error('WorkBuddy query timed out')))
|
|
36
|
-
req.end()
|
|
37
|
-
})
|
|
38
|
-
}
|
|
1
|
+
import { request } from 'node:http'
|
|
2
|
+
|
|
3
|
+
export function workBuddyOrigin(value) {
|
|
4
|
+
let url
|
|
5
|
+
try { url = new URL(value) } catch {}
|
|
6
|
+
if (!url || url.protocol !== 'http:' || !['127.0.0.1', '[::1]', 'localhost'].includes(url.hostname)
|
|
7
|
+
|| url.username || url.password || url.pathname !== '/' || url.search || url.hash) {
|
|
8
|
+
throw Object.assign(new Error('workbuddyConnectUnavailable'), { code: 'workbuddyConnectUnavailable' })
|
|
9
|
+
}
|
|
10
|
+
return url
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Host-local reads must not use DSH's global fetch proxy/dispatcher. No credentials,
|
|
14
|
+
// redirects, or configurable remote paths; the byte limit includes the whole body.
|
|
15
|
+
export function fetchWorkBuddyLocal(url, { signal } = {}) {
|
|
16
|
+
workBuddyOrigin(new URL(url).origin)
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const req = request(url, { method: 'GET', agent: false, signal, headers: { accept: 'application/json', 'accept-encoding': 'identity' } }, res => {
|
|
19
|
+
const chunks = []; let bytes = 0
|
|
20
|
+
res.on('error', reject)
|
|
21
|
+
res.on('data', chunk => {
|
|
22
|
+
bytes += chunk.length
|
|
23
|
+
if (bytes > 1024 * 1024) { res.destroy(new Error('WorkBuddy response too large')); return }
|
|
24
|
+
chunks.push(chunk)
|
|
25
|
+
})
|
|
26
|
+
res.on('end', () => {
|
|
27
|
+
const status = res.statusCode ?? 502
|
|
28
|
+
if (status >= 300 && status < 400) { reject(new TypeError('fetch failed: redirect refused')); return }
|
|
29
|
+
resolve(new Response([204, 205, 304].includes(status) ? null : Buffer.concat(chunks), {
|
|
30
|
+
status, headers: { 'content-type': res.headers['content-type'] ?? '' },
|
|
31
|
+
}))
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
req.on('error', reject)
|
|
35
|
+
req.setTimeout(35000, () => req.destroy(new Error('WorkBuddy query timed out')))
|
|
36
|
+
req.end()
|
|
37
|
+
})
|
|
38
|
+
}
|