@herenfor/dsh-wsl 0.0.0-stage → 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/LICENSE +51 -0
- package/README.md +75 -2
- package/README.zh.md +76 -0
- package/agent/broker.mjs +107 -0
- package/agent/cordis.patch.yml +3 -0
- package/agent/index.mjs +130 -0
- package/agent/package.json +9 -0
- package/client.js +825 -0
- package/cordis.patch.yml +3 -0
- package/icon.svg +1 -0
- package/ids.mjs +89 -0
- package/index.mjs +614 -0
- package/language.mjs +27 -0
- package/locale/en.json +7 -0
- package/locale/zh.json +7 -0
- package/merge.mjs +65 -0
- package/package.json +59 -4
- package/paths.mjs +24 -0
- package/plugins.mjs +77 -0
- package/remote-mux.mjs +154 -0
- package/routes.mjs +47 -0
- package/routing.mjs +507 -0
- package/selfupdate.mjs +79 -0
- package/sessions.mjs +101 -0
- package/settings.mjs +53 -0
- package/setup.mjs +286 -0
- package/transport/client.mjs +102 -0
- package/transport/daemon.mjs +268 -0
- package/transport/mux.mjs +118 -0
package/cordis.patch.yml
ADDED
package/icon.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 16 16"><ellipse cx="8" cy="8.8" rx="5" ry="6.2" fill="#1f1f1f" stroke="#8a8a8a" stroke-width=".5"/><ellipse cx="8" cy="10.4" rx="3.3" ry="4.1" fill="#fafafa"/><circle cx="6.6" cy="5.3" r="1.05" fill="#fafafa"/><circle cx="9.4" cy="5.3" r="1.05" fill="#fafafa"/><circle cx="6.8" cy="5.5" r=".5" fill="#1f1f1f"/><circle cx="9.2" cy="5.5" r=".5" fill="#1f1f1f"/><path d="M6.5 6.7h3L8 8.2z" fill="#f5a623"/><ellipse cx="5.5" cy="14.9" rx="2.1" ry=".95" fill="#f5a623"/><ellipse cx="10.5" cy="14.9" rx="2.1" ry=".95" fill="#f5a623"/></svg>
|
package/ids.mjs
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Session and Workspace identities of the WSL Host, as the page sees them: prefixed with `wsl:` so they cannot collide
|
|
2
|
+
// with this Host's own and so every call that names one can be sent to the right Host.
|
|
3
|
+
//
|
|
4
|
+
// The list of identity-bearing fields and the special cases for `id`, `owner` and `val` are adapted from
|
|
5
|
+
// dsh-ssh-workspaces (src/backend-protocol.ts), MIT License, Copyright (c) 2026 czp0312 and dsh-ssh-workspaces
|
|
6
|
+
// contributors. Kept free of DSH imports so it can be unit-tested.
|
|
7
|
+
|
|
8
|
+
export const PREFIX = 'wsl:'
|
|
9
|
+
|
|
10
|
+
const IDENTITY = /^(?:workspaceIds?|beforeWorkspaceId|(?:[a-zA-Z]*Session|session)Ids?|parentSession|agentId|workspaceFileScopeId|eventId)$/
|
|
11
|
+
const resource = (value) => value?.kind === 'session' || value?.kind === 'workspace'
|
|
12
|
+
const member = (value) => (value?.role === 'lead' || value?.role === 'teammate') && typeof value?.name === 'string'
|
|
13
|
+
|
|
14
|
+
export const isWsl = (id) => typeof id === 'string' && id.startsWith(PREFIX)
|
|
15
|
+
export const toPage = (id) => `${PREFIX}${id}`
|
|
16
|
+
export const toWsl = (id) => (isWsl(id) ? id.slice(PREFIX.length) : id)
|
|
17
|
+
|
|
18
|
+
/** The field name an identity value is read under: `id` of a session or workspace reference counts as `sessionId`. */
|
|
19
|
+
function fieldOf(object, name, key) {
|
|
20
|
+
if (name === 'id' && (resource(object) || member(object) || key === 'subagentCatalog' || (key === 'header' && typeof object.version === 'number'))) return 'sessionId'
|
|
21
|
+
if (name === 'owner' && typeof object.startedAt === 'number' && object.output && typeof object.kind === 'string') return 'sessionId'
|
|
22
|
+
if (name === 'val' && typeof object.key === 'string') return object.key
|
|
23
|
+
return name
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Rewrite every identity in a value. `incoming` (WSL Host → page) adds the prefix; outgoing removes it and refuses a
|
|
28
|
+
* value that mixes this Host's identities with the WSL Host's.
|
|
29
|
+
*/
|
|
30
|
+
export function translate(value, incoming, key = '') {
|
|
31
|
+
if (typeof value === 'string' && IDENTITY.test(key)) return incoming ? toPage(value) : toWsl(value)
|
|
32
|
+
if (Array.isArray(value)) return value.map((item) => translate(item, incoming, key))
|
|
33
|
+
if (value && typeof value === 'object' && !ArrayBuffer.isView(value)) {
|
|
34
|
+
const out = {}
|
|
35
|
+
for (const [name, item] of Object.entries(value)) out[name] = translate(item, incoming, fieldOf(value, name, key))
|
|
36
|
+
return out
|
|
37
|
+
}
|
|
38
|
+
return value
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Every identity a value names, with the field it was found under. */
|
|
42
|
+
export function identities(value) {
|
|
43
|
+
const found = []
|
|
44
|
+
const walk = (item, key = '') => {
|
|
45
|
+
if (typeof item === 'string' && IDENTITY.test(key)) found.push({ key, value: item })
|
|
46
|
+
else if (Array.isArray(item)) item.forEach((child) => walk(child, key))
|
|
47
|
+
else if (item && typeof item === 'object' && !ArrayBuffer.isView(item)) for (const [name, child] of Object.entries(item)) walk(child, fieldOf(item, name, key))
|
|
48
|
+
}
|
|
49
|
+
walk(value)
|
|
50
|
+
return found
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Which Host a call belongs to: 'wsl' when it names only WSL identities, 'local' when it names only this Host's or
|
|
55
|
+
* none. A call naming both is refused (the error says why), except the identity of a new Session in `session/create`,
|
|
56
|
+
* which the router decides itself.
|
|
57
|
+
*/
|
|
58
|
+
export function routeOf(payload, endpoint = '') {
|
|
59
|
+
const items = identities(payload).filter((item) => !(endpoint === 'session/create' && item.key === 'sessionId'))
|
|
60
|
+
const wsl = items.filter((item) => isWsl(item.value)).length
|
|
61
|
+
if (wsl === 0) return 'local'
|
|
62
|
+
if (wsl === items.length) return 'wsl'
|
|
63
|
+
throw Object.assign(new Error('This action mixes a Windows session or folder with a WSL one.'), { code: 'wsl/mixed-hosts' })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Plugins installed only in WSL talk to their own Host part over Fetch routes with bodies of their own shape, so the
|
|
67
|
+
// identities in them are found by value, not by field: a Session id has a fixed form, a Workspace id is one the WSL
|
|
68
|
+
// Host lists. Map keys count as values (a plugin may index by Session).
|
|
69
|
+
const UUID = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
|
|
70
|
+
const SESSION_ID = new RegExp(`^session-${UUID}$`)
|
|
71
|
+
const PAGE_ID = new RegExp(`^${PREFIX}(?:session-)?${UUID}$`)
|
|
72
|
+
|
|
73
|
+
/** `incoming` (WSL Host → page) prefixes WSL Session ids and the given WSL Workspace ids; outgoing strips the prefix. */
|
|
74
|
+
export function translateLoose(value, incoming, workspaceIds = new Set()) {
|
|
75
|
+
const one = (text) => incoming
|
|
76
|
+
? (SESSION_ID.test(text) || workspaceIds.has(text) ? toPage(text) : text)
|
|
77
|
+
: (PAGE_ID.test(text) ? toWsl(text) : text)
|
|
78
|
+
const walk = (item) => {
|
|
79
|
+
if (typeof item === 'string') return one(item)
|
|
80
|
+
if (Array.isArray(item)) return item.map(walk)
|
|
81
|
+
if (item && typeof item === 'object' && !ArrayBuffer.isView(item)) {
|
|
82
|
+
const out = {}
|
|
83
|
+
for (const [name, child] of Object.entries(item)) out[one(name)] = walk(child)
|
|
84
|
+
return out
|
|
85
|
+
}
|
|
86
|
+
return item
|
|
87
|
+
}
|
|
88
|
+
return walk(value)
|
|
89
|
+
}
|