dsh-github-router 0.3.2 → 0.4.0
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/CHANGELOG.md +9 -0
- package/CONTRIBUTING.md +17 -6
- package/README.md +26 -18
- package/README.zh.md +22 -17
- package/docs/design.md +46 -40
- package/lib/client.js +165 -116
- package/lib/config.js +167 -116
- package/lib/index.js +11 -6
- package/lib/settings.js +23 -21
- package/package.json +11 -17
package/lib/config.js
CHANGED
|
@@ -1,116 +1,167 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Plugin configuration schema and runtime-option resolution for
|
|
3
|
+
* dsh-github-router.
|
|
4
|
+
*
|
|
5
|
+
* The schema IS the settings surface. DSH reads the `Config` schema a plugin
|
|
6
|
+
* exports from its Loader entry, serves that entry's id
|
|
7
|
+
* (`dsh-github-router`) as the settings namespace to the browser, derives the
|
|
8
|
+
* configuration form from the schema, validates every write against it, and
|
|
9
|
+
* redacts `.role('secret')` fields on every wire boundary. There is no
|
|
10
|
+
* registration call anymore — `lib/settings.js` only reads the live values.
|
|
11
|
+
*
|
|
12
|
+
* Every field is `.volatile()`, which is what makes it editable from the
|
|
13
|
+
* configuration page AND live: the framework hands the plugin a Volatile
|
|
14
|
+
* reference whose `.get()` is the current value (user layer over composition
|
|
15
|
+
* layer over schema default), and a saved write applies to the NEXT tool call.
|
|
16
|
+
*
|
|
17
|
+
* The schema is deliberately FLAT: the plugin's card writes scalar fields by
|
|
18
|
+
* name, so nested objects cannot be edited from it. `resolveOptions` projects
|
|
19
|
+
* the flat section into the nested runtime shape the core aggregators use.
|
|
20
|
+
* @module dsh-github-router/config
|
|
21
|
+
*/
|
|
22
|
+
import z from '@deepseek-ai/schemastery'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Settings namespace carrying the plugin's configuration.
|
|
26
|
+
*
|
|
27
|
+
* Since DSH 0.1.7-rc.2 the namespace is the Loader entry id the bundle patch
|
|
28
|
+
* declares (`cordis.patch.yml` inserts the row as `dsh-github-router`); the
|
|
29
|
+
* browser half addresses the same string through `ctx.configForms.get(...)`
|
|
30
|
+
* and keys its Plugins-page card with it.
|
|
31
|
+
*/
|
|
32
|
+
export const NAMESPACE = 'dsh-github-router'
|
|
33
|
+
|
|
34
|
+
/** Composition/settings schema. All fields optional with safe defaults. */
|
|
35
|
+
export const Config = z.object({
|
|
36
|
+
/** Literal GitHub token (redacted on wire). Prefer tokenEnv. */
|
|
37
|
+
token: z.string().role('secret').volatile(),
|
|
38
|
+
/** Environment variable / credential ref naming the token. */
|
|
39
|
+
tokenEnv: z.string().role('credential-ref').default('GITHUB_TOKEN').volatile(),
|
|
40
|
+
/** Proxy URL for proxy routes. '' = inherit ambient proxy env; 'direct' = never proxy. */
|
|
41
|
+
proxy: z.string().default('').volatile(),
|
|
42
|
+
directTimeoutMs: z.number().min(1000).max(60000).default(8000).volatile(),
|
|
43
|
+
proxyTimeoutMs: z.number().min(1000).max(60000).default(15000).volatile(),
|
|
44
|
+
/** Retries for idempotent GETs on 429/5xx. */
|
|
45
|
+
retries: z.number().min(0).max(3).default(1).volatile(),
|
|
46
|
+
routesApi: z.boolean().default(true).volatile(),
|
|
47
|
+
routesGh: z.boolean().default(true).volatile(),
|
|
48
|
+
routesGit: z.boolean().default(true).volatile(),
|
|
49
|
+
routesHtml: z.boolean().default(true).volatile(),
|
|
50
|
+
/** Raw-content mirrors: OFF by default — the user must opt in. */
|
|
51
|
+
routesMirror: z.boolean().default(false).volatile(),
|
|
52
|
+
/** Raw-content mirror base URLs, e.g. ["https://ghproxy.net"]. Empty = none. */
|
|
53
|
+
mirrors: z.array(z.string()).default([]).volatile(),
|
|
54
|
+
/** PR/issue metadata cache TTL. */
|
|
55
|
+
cacheTtlMeta: z.number().min(0).default(300).volatile(),
|
|
56
|
+
/** Immutable-ish content (files, commits, diffs at a sha) cache TTL. */
|
|
57
|
+
cacheTtlContent: z.number().min(0).default(86400).volatile(),
|
|
58
|
+
/** Cap for every response body read by this plugin, in bytes. */
|
|
59
|
+
maxBytes: z.number().min(16384).max(8388608).default(1048576).volatile(),
|
|
60
|
+
/** Local repo paths granted for read-only git-route reads (log/diff/show only). */
|
|
61
|
+
repos: z.array(z.string()).default([]).volatile(),
|
|
62
|
+
/** Plugin-owned git fetch cache dir. '' = <DSH_HOME>/storages/dsh-github-router/git. */
|
|
63
|
+
gitCacheDir: z.string().default('').volatile(),
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
/** Every schema field, in the order the configuration card renders them. */
|
|
67
|
+
const CONFIG_FIELDS = [
|
|
68
|
+
'token',
|
|
69
|
+
'tokenEnv',
|
|
70
|
+
'proxy',
|
|
71
|
+
'directTimeoutMs',
|
|
72
|
+
'proxyTimeoutMs',
|
|
73
|
+
'retries',
|
|
74
|
+
'routesApi',
|
|
75
|
+
'routesGh',
|
|
76
|
+
'routesGit',
|
|
77
|
+
'routesHtml',
|
|
78
|
+
'routesMirror',
|
|
79
|
+
'mirrors',
|
|
80
|
+
'cacheTtlMeta',
|
|
81
|
+
'cacheTtlContent',
|
|
82
|
+
'maxBytes',
|
|
83
|
+
'repos',
|
|
84
|
+
'gitCacheDir',
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Read one live configuration section out of the config the framework passes
|
|
89
|
+
* to `apply(ctx, config)`.
|
|
90
|
+
*
|
|
91
|
+
* A `.volatile()` field arrives as a Volatile reference, so its current value
|
|
92
|
+
* is read with `.get()` at the moment of the call; a plain value (a hand-built
|
|
93
|
+
* options object, or a unit test) passes through unchanged. Reading per call is
|
|
94
|
+
* what makes a settings write apply to the next tool call.
|
|
95
|
+
* @param config - the resolved plugin config, volatile references or plain values.
|
|
96
|
+
* @returns a flat section usable by {@link resolveOptions}.
|
|
97
|
+
*/
|
|
98
|
+
export function liveSection(config) {
|
|
99
|
+
const source = config ?? {}
|
|
100
|
+
const section = {}
|
|
101
|
+
for (const field of CONFIG_FIELDS) {
|
|
102
|
+
const value = source[field]
|
|
103
|
+
section[field] =
|
|
104
|
+
value !== null && typeof value === 'object' && typeof value.get === 'function'
|
|
105
|
+
? value.get()
|
|
106
|
+
: value
|
|
107
|
+
}
|
|
108
|
+
return section
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const PROXY_ENV_NAMES = ['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy']
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Resolve one settings/composition section into fully-defaulted runtime
|
|
115
|
+
* options. A thunk-producing source is consumed at call time so a settings
|
|
116
|
+
* change applies to the NEXT tool call.
|
|
117
|
+
* @param section - the resolved settings section (already schema-defaulted).
|
|
118
|
+
*/
|
|
119
|
+
export function resolveOptions(section) {
|
|
120
|
+
const s = section ?? {}
|
|
121
|
+
const proxy =
|
|
122
|
+
typeof s.proxy === 'string' && s.proxy.trim().length > 0
|
|
123
|
+
? s.proxy.trim()
|
|
124
|
+
: undefined
|
|
125
|
+
const routes = {
|
|
126
|
+
api: s.routesApi !== false,
|
|
127
|
+
gh: s.routesGh !== false,
|
|
128
|
+
git: s.routesGit !== false,
|
|
129
|
+
html: s.routesHtml !== false,
|
|
130
|
+
mirror: s.routesMirror === true,
|
|
131
|
+
}
|
|
132
|
+
const mirrors = Array.isArray(s.mirrors)
|
|
133
|
+
? s.mirrors.filter((m) => typeof m === 'string' && m.trim().length > 0).map((m) => m.trim())
|
|
134
|
+
: []
|
|
135
|
+
const repos = Array.isArray(s.repos)
|
|
136
|
+
? s.repos.filter((r) => typeof r === 'string' && r.trim().length > 0).map((r) => r.trim())
|
|
137
|
+
: []
|
|
138
|
+
return {
|
|
139
|
+
token: typeof s.token === 'string' && s.token.length > 0 ? s.token : undefined,
|
|
140
|
+
tokenEnv: typeof s.tokenEnv === 'string' && s.tokenEnv.length > 0 ? s.tokenEnv : 'GITHUB_TOKEN',
|
|
141
|
+
proxy, // undefined = ambient env decides; 'direct' = never proxy
|
|
142
|
+
directTimeoutMs: Number.isFinite(s.directTimeoutMs) ? s.directTimeoutMs : 8000,
|
|
143
|
+
proxyTimeoutMs: Number.isFinite(s.proxyTimeoutMs) ? s.proxyTimeoutMs : 15000,
|
|
144
|
+
retries: Number.isFinite(s.retries) ? Math.min(3, Math.max(0, s.retries)) : 1,
|
|
145
|
+
routes,
|
|
146
|
+
mirrors,
|
|
147
|
+
cacheTtlSeconds: {
|
|
148
|
+
meta: Number.isFinite(s.cacheTtlMeta) ? s.cacheTtlMeta : 300,
|
|
149
|
+
content: Number.isFinite(s.cacheTtlContent) ? s.cacheTtlContent : 86400,
|
|
150
|
+
},
|
|
151
|
+
maxBytes: Number.isFinite(s.maxBytes) ? Math.min(8388608, Math.max(16384, s.maxBytes)) : 1048576,
|
|
152
|
+
repos,
|
|
153
|
+
gitCacheDir: typeof s.gitCacheDir === 'string' && s.gitCacheDir.trim().length > 0 ? s.gitCacheDir.trim() : undefined,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** The proxy URL one route attempt uses: explicit override > ambient env > none. */
|
|
158
|
+
export function effectiveProxy(options, explicit) {
|
|
159
|
+
if (explicit !== undefined && explicit !== null && explicit !== '') return explicit
|
|
160
|
+
if (options.proxy === 'direct') return undefined
|
|
161
|
+
if (options.proxy !== undefined && options.proxy !== '') return options.proxy
|
|
162
|
+
for (const name of PROXY_ENV_NAMES) {
|
|
163
|
+
const value = process.env[name]
|
|
164
|
+
if (value && value.length > 0) return value
|
|
165
|
+
}
|
|
166
|
+
return undefined
|
|
167
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
* 4. Plugin-owned state (fetch cache, response cache) lives under
|
|
20
20
|
* <DSH_HOME>/storages/dsh-github-router; user repositories are only ever
|
|
21
21
|
* read (log/diff/show) and only when explicitly granted.
|
|
22
|
-
* 5. Configuration
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* route exists.
|
|
22
|
+
* 5. Configuration is the exported `Config` schema (DSH ≥ 0.1.7-rc.2): the
|
|
23
|
+
* framework serves the `dsh-github-router` entry id as the settings
|
|
24
|
+
* namespace, derives the browser page from the schema, validates and
|
|
25
|
+
* persists writes, and redacts the secret token field on every wire
|
|
26
|
+
* boundary. The browser half renders that page in Settings → Plugins on
|
|
27
|
+
* the bundle's row, so no plugin-owned HTTP route exists.
|
|
28
28
|
* @module dsh-github-router
|
|
29
29
|
*/
|
|
30
30
|
import { installSettings } from './settings.js'
|
|
@@ -36,6 +36,11 @@ import { registerIssueTool } from './tools/issue.js'
|
|
|
36
36
|
import { registerPrTool } from './tools/pr.js'
|
|
37
37
|
import { registerProbeTool } from './tools/probe.js'
|
|
38
38
|
|
|
39
|
+
// The framework reads this entry's `Config` export as the settings schema and
|
|
40
|
+
// serves the entry id as its namespace; re-exporting keeps the schema in the
|
|
41
|
+
// module the Loader checks.
|
|
42
|
+
export { Config } from './config.js'
|
|
43
|
+
|
|
39
44
|
export const name = 'dsh-github-router'
|
|
40
45
|
export const inject = ['tools', 'subprocess', 'skills', 'systemPrompt']
|
|
41
46
|
|
package/lib/settings.js
CHANGED
|
@@ -1,38 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Host-side settings wiring for dsh-github-router.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Since DSH 0.1.7-rc.2 a plugin declares its configuration by EXPORTING a
|
|
5
|
+
* `Config` schema from its Loader entry (see `lib/index.js` and
|
|
6
|
+
* `lib/config.js`): the framework serves the entry id as the settings
|
|
7
|
+
* namespace to the (loopback) browser, derives the configuration form from the
|
|
8
|
+
* schema, validates and persists every write with revision fencing, redacts
|
|
9
|
+
* `role('secret')` fields on every wire boundary, and hands the plugin Volatile
|
|
10
|
+
* references for the live values. The old
|
|
11
|
+
* `ctx.settings.register(namespace, schema, …)` seam no longer exists, so this
|
|
12
|
+
* module keeps only the two things that remain plugin-owned:
|
|
8
13
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* applies to the NEXT tool call.
|
|
14
|
+
* 1. `configure({ auto: false })` — the plugin ships its own page
|
|
15
|
+
* (`plugins.row.config`, see `lib/client.js`), so the framework must not
|
|
16
|
+
* auto-generate a second one for the same namespace.
|
|
17
|
+
* 2. `options()` — project the live volatile section into the runtime options
|
|
18
|
+
* the tools consume. The section is read per call, so a saved settings
|
|
19
|
+
* change applies to the NEXT tool call.
|
|
16
20
|
* @module dsh-github-router/settings
|
|
17
21
|
*/
|
|
18
|
-
import {
|
|
22
|
+
import { liveSection, resolveOptions } from './config.js'
|
|
19
23
|
|
|
20
24
|
/**
|
|
21
|
-
*
|
|
25
|
+
* Wire the plugin's settings surface and return the runtime-options thunk.
|
|
22
26
|
* @param ctx - the plugin context.
|
|
23
|
-
* @param config - the
|
|
27
|
+
* @param config - the config the framework resolved for this entry (volatile references).
|
|
24
28
|
*/
|
|
25
29
|
export function installSettings(ctx, config) {
|
|
26
|
-
const holder = { scope: null }
|
|
27
|
-
|
|
28
30
|
ctx.inject(['settings'], (sctx) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
sctx.effect(
|
|
32
|
+
() => sctx.settings.configure({ auto: false }, ctx.fiber),
|
|
33
|
+
'dsh-github-router: settings presentation',
|
|
34
|
+
)
|
|
33
35
|
})
|
|
34
36
|
|
|
35
37
|
return {
|
|
36
|
-
options: () =>
|
|
38
|
+
options: () => resolveOptions(liveSection(config)),
|
|
37
39
|
}
|
|
38
40
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-github-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "DeepSeek Harness plugin: read-only GitHub access for agents (github_probe / github_pr / github_issue / github_file / github_api). Routes every request inside the tool — api.github.com (direct or proxy), gh CLI, git protocol (plugin-owned fetch cache plus read-only local repo reads), PR/issue page HTML with strict JSON embeddedData extraction, and optional user-configured raw mirrors — so agents never burn turns fighting sandbox TLS/proxy failures in a shell. No write/push capability exists anywhere in the plugin.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh-plugin",
|
|
@@ -47,36 +47,30 @@
|
|
|
47
47
|
],
|
|
48
48
|
"dsh": {
|
|
49
49
|
"compatibility": {
|
|
50
|
-
"node": ">=
|
|
50
|
+
"node": ">=22.19.0",
|
|
51
51
|
"dshReleases": {
|
|
52
|
-
"0.1.
|
|
53
|
-
"0.1.2-alpha.
|
|
54
|
-
"0.1.1-rc.2": "incompatible"
|
|
52
|
+
"0.1.7-rc.2": "compatible",
|
|
53
|
+
"0.1.2-alpha.4": "incompatible"
|
|
55
54
|
}
|
|
56
55
|
},
|
|
57
56
|
"bundle": {
|
|
58
57
|
"patch": "./cordis.patch.yml"
|
|
59
58
|
},
|
|
60
59
|
"client": {
|
|
61
|
-
"platform": "web"
|
|
62
|
-
"inject": [
|
|
63
|
-
"@deepseek-ai/dsh-client-store"
|
|
64
|
-
]
|
|
60
|
+
"platform": "web"
|
|
65
61
|
}
|
|
66
62
|
},
|
|
67
63
|
"engines": {
|
|
68
|
-
"node": ">=
|
|
64
|
+
"node": ">=22.19.0"
|
|
69
65
|
},
|
|
70
66
|
"peerDependencies": {
|
|
71
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
72
|
-
"@deepseek-ai/dsh-
|
|
73
|
-
"@deepseek-ai/dsh-
|
|
74
|
-
"@deepseek-ai/
|
|
75
|
-
"@deepseek-ai/schemastery": "^3.18.1"
|
|
67
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
68
|
+
"@deepseek-ai/dsh-credentials": "^0.1.7-rc.2",
|
|
69
|
+
"@deepseek-ai/dsh-tools": "^0.1.7-rc.2",
|
|
70
|
+
"@deepseek-ai/schemastery": "^3.18.4"
|
|
76
71
|
},
|
|
77
72
|
"devDependencies": {
|
|
78
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
79
|
-
"@deepseek-ai/dsh-tools": "^0.1.2-alpha.1"
|
|
73
|
+
"@deepseek-ai/cordis": "^4.0.4"
|
|
80
74
|
},
|
|
81
75
|
"publishConfig": {
|
|
82
76
|
"access": "public"
|