dsh-hitl 0.1.1 → 0.2.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/README.md +74 -19
- package/README.zh.md +66 -18
- package/index.js +50 -13
- package/lib/fields.js +224 -19
- package/lib/protocol.js +13 -0
- package/lib/resolve.js +67 -6
- package/package.json +1 -2
- package/tests/docs.test.js +21 -16
- package/tests/fields.test.js +119 -1
- package/tests/manifest.test.js +16 -0
- package/tests/resolve.test.js +62 -0
- package/docs/01-panel-cn.png +0 -0
- package/docs/01-panel-en.png +0 -0
- package/docs/02-fields-cn.png +0 -0
- package/docs/02-fields-en.png +0 -0
- package/docs/03-subagent-cn.png +0 -0
- package/docs/03-subagent-en.png +0 -0
- package/docs/04-overlay-cn.png +0 -0
- package/docs/04-overlay-en.png +0 -0
package/tests/manifest.test.js
CHANGED
|
@@ -24,6 +24,22 @@ const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
|
|
|
24
24
|
const LANGUAGES = ['en', 'zh']
|
|
25
25
|
|
|
26
26
|
describe('manifest: what a profile reads about this plugin', () => {
|
|
27
|
+
it('keeps the tag-triggered release on stage-only trusted publishing', () => {
|
|
28
|
+
const workflow = readFileSync(join(root, '.github', 'workflows', 'release.yml'), 'utf8')
|
|
29
|
+
assert.equal(workflow.includes("tags: ['v*']"), true, 'the workflow must trigger on version tags')
|
|
30
|
+
assert.equal(workflow.includes('id-token: write'), true, 'OIDC publishing needs the id-token permission')
|
|
31
|
+
assert.equal(workflow.includes('contents: read'), true)
|
|
32
|
+
// Stage-only: the workflow may upload a release, but a maintainer approves
|
|
33
|
+
// it with 2FA before it becomes public, so it must never publish directly.
|
|
34
|
+
assert.equal(/run: npm stage publish/.test(workflow), true, 'the release step must stage')
|
|
35
|
+
assert.equal(/run: npm publish\b/.test(workflow), false, 'a direct publish would need "publish directly" granted')
|
|
36
|
+
// Provenance is automatic under trusted publishing — but this file *talks*
|
|
37
|
+
// about that, so assert on the command, not on the words.
|
|
38
|
+
assert.equal(/npm stage publish[^\n]*--provenance/.test(workflow), false, 'no --provenance flag on the command')
|
|
39
|
+
// npm matches the configured trusted publisher against this exact filename.
|
|
40
|
+
assert.equal(workflow.includes('release.yml'), true, 'the trusted publisher is configured with this filename')
|
|
41
|
+
})
|
|
42
|
+
|
|
27
43
|
it('resolves the resources the Loader asks for by name', () => {
|
|
28
44
|
assert.equal(manifest.name, 'dsh-hitl')
|
|
29
45
|
assert.equal(typeof manifest.description, 'string')
|
package/tests/resolve.test.js
CHANGED
|
@@ -61,6 +61,7 @@ describe('resolve: normalizeMount', () => {
|
|
|
61
61
|
whenUnavailable: 'reject',
|
|
62
62
|
enabled: true,
|
|
63
63
|
maxFieldChars: DEFAULT_OPTIONS.maxFieldChars,
|
|
64
|
+
resolveTimeoutMs: DEFAULT_OPTIONS.resolveTimeoutMs,
|
|
64
65
|
})
|
|
65
66
|
})
|
|
66
67
|
|
|
@@ -79,6 +80,25 @@ describe('resolve: normalizeMount', () => {
|
|
|
79
80
|
assert.match(warnings[0], /countdown.seconds/)
|
|
80
81
|
})
|
|
81
82
|
|
|
83
|
+
it('warns when a mount waits forever with nothing to end the wait', () => {
|
|
84
|
+
// Fail-open plus no deadline is legal, and it blocks an unattended call for
|
|
85
|
+
// as long as its session lives. The mount is still installed; the warning is
|
|
86
|
+
// the whole point, because the first unattended run must not be the discovery.
|
|
87
|
+
const open = normalizeMount({ tool: 'bash', whenUnavailable: 'wait' }, 'src')
|
|
88
|
+
assert.equal(open.ok, true)
|
|
89
|
+
assert.equal(open.mount.options.whenUnavailable, 'wait')
|
|
90
|
+
assert.equal(open.warnings.length, 1)
|
|
91
|
+
assert.match(open.warnings[0], /whenUnavailable: wait without a countdown/)
|
|
92
|
+
|
|
93
|
+
// A deadline is what bounds it, so the pairing is silent — and so is the
|
|
94
|
+
// fail-closed default, which needs no deadline to be safe.
|
|
95
|
+
const bounded = normalizeMount({ tool: 'bash', whenUnavailable: 'wait', countdown: { seconds: 300, action: 'reject' } }, 'src')
|
|
96
|
+
assert.equal(bounded.warnings.length, 0)
|
|
97
|
+
const closed = normalizeMount({ tool: 'bash' }, 'src')
|
|
98
|
+
assert.equal(closed.mount.options.whenUnavailable, 'reject')
|
|
99
|
+
assert.equal(closed.warnings.length, 0)
|
|
100
|
+
})
|
|
101
|
+
|
|
82
102
|
it('normalizes field entries and drops invalid ones', () => {
|
|
83
103
|
const { mount, warnings } = normalizeMount({
|
|
84
104
|
tool: 'edit',
|
|
@@ -92,6 +112,48 @@ describe('resolve: normalizeMount', () => {
|
|
|
92
112
|
assert.match(warnings[0], /fields\[2\]\.render/)
|
|
93
113
|
})
|
|
94
114
|
|
|
115
|
+
it('keeps a literal or computed field value and drops an unusable one', () => {
|
|
116
|
+
const read = () => 'value'
|
|
117
|
+
const { mount, warnings } = normalizeMount({
|
|
118
|
+
tool: 'edit',
|
|
119
|
+
fields: [
|
|
120
|
+
{ param: 'note', value: '固定文案' },
|
|
121
|
+
{ param: 'computed', value: read },
|
|
122
|
+
{ param: 'content', value: undefined, diff: { path: 'file_path', before: read, after: 'new_string' } },
|
|
123
|
+
],
|
|
124
|
+
}, 'src')
|
|
125
|
+
assert.equal(mount.options.fields[0].value, '固定文案')
|
|
126
|
+
assert.equal(mount.options.fields[1].value, read)
|
|
127
|
+
assert.deepEqual(Object.keys(mount.options.fields[2].diff), ['before', 'after', 'path'])
|
|
128
|
+
assert.equal(mount.options.fields[2].diff.before, read)
|
|
129
|
+
assert.equal(warnings.length, 0)
|
|
130
|
+
|
|
131
|
+
const bad = normalizeMount({ tool: 'edit', fields: [{ param: 'x', value: 7 }] }, 'src')
|
|
132
|
+
assert.deepEqual(bad.mount.options.fields, [])
|
|
133
|
+
assert.equal(bad.warnings.length, 1)
|
|
134
|
+
assert.match(bad.warnings[0], /fields\[0\]\.value/)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('drops a malformed diff instead of letting the field render as markdown', () => {
|
|
138
|
+
const bad = normalizeMount({ tool: 'edit', diff: { before: 'old_string' } }, 'src')
|
|
139
|
+
assert.equal(bad.mount.options.diff, undefined)
|
|
140
|
+
assert.equal(bad.warnings.length, 1)
|
|
141
|
+
assert.match(bad.warnings[0], /^src: diff\.before and diff\.after/)
|
|
142
|
+
|
|
143
|
+
const fieldLevel = normalizeMount({
|
|
144
|
+
tool: 'edit', fields: [{ param: 'x', diff: { before: 'a', after: 'b', path: 7 } }],
|
|
145
|
+
}, 'src')
|
|
146
|
+
assert.equal('diff' in fieldLevel.mount.options.fields[0], false)
|
|
147
|
+
assert.equal(fieldLevel.warnings.length, 1)
|
|
148
|
+
assert.match(fieldLevel.warnings[0], /fields\[0\]\.diff\.path/)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('bounds a computed field with resolveTimeoutMs, defaulting from the protocol', () => {
|
|
152
|
+
assert.equal(normalizeMount({ tool: 'bash' }, 'src').mount.options.resolveTimeoutMs, DEFAULT_OPTIONS.resolveTimeoutMs)
|
|
153
|
+
assert.equal(normalizeMount({ tool: 'bash', resolveTimeoutMs: 250 }, 'src').mount.options.resolveTimeoutMs, 250)
|
|
154
|
+
assert.equal(normalizeMount({ tool: 'bash', resolveTimeoutMs: 0 }, 'src').mount.options.resolveTimeoutMs, DEFAULT_OPTIONS.resolveTimeoutMs)
|
|
155
|
+
})
|
|
156
|
+
|
|
95
157
|
it('reports a bad layout, unavailable policy, and modify mode', () => {
|
|
96
158
|
const { mount, warnings } = normalizeMount({
|
|
97
159
|
tool: 'bash', layout: 'grid', whenUnavailable: 'maybe', modify: { mode: 'ignore' }, maxFieldChars: -1, labels: ['ok', 3],
|
package/docs/01-panel-cn.png
DELETED
|
Binary file
|
package/docs/01-panel-en.png
DELETED
|
Binary file
|
package/docs/02-fields-cn.png
DELETED
|
Binary file
|
package/docs/02-fields-en.png
DELETED
|
Binary file
|
package/docs/03-subagent-cn.png
DELETED
|
Binary file
|
package/docs/03-subagent-en.png
DELETED
|
Binary file
|
package/docs/04-overlay-cn.png
DELETED
|
Binary file
|
package/docs/04-overlay-en.png
DELETED
|
Binary file
|