@duke-dsh-plugins/dsh-agent-approval 1.3.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/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@duke-dsh-plugins/dsh-agent-approval",
3
+ "version": "1.3.0",
4
+ "description": "Agent-decided approvals for DeepSeek Harness: a workspace-write base permission mode where an independent approval subagent judges every sandbox escalation (risky operations are rejected), with a configurable approval model and an audit log in Settings.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/MoonlitDropOfBlood/dsh-agent-approval.git"
8
+ },
9
+ "homepage": "https://github.com/MoonlitDropOfBlood/dsh-agent-approval",
10
+ "bugs": { "url": "https://github.com/MoonlitDropOfBlood/dsh-agent-approval/issues" },
11
+ "publishConfig": { "access": "public" },
12
+ "keywords": ["dsh", "deepseek-harness", "plugin", "approval", "security"],
13
+ "type": "module",
14
+ "main": "index.js",
15
+ "exports": {
16
+ ".": "./index.js",
17
+ "./client": "./client.js",
18
+ "./typert": "./typert.host.js",
19
+ "./cordis.patch.yml": "./cordis.patch.yml",
20
+ "./package.json": "./package.json"
21
+ },
22
+ "files": [
23
+ "index.js",
24
+ "client.js",
25
+ "typert.host.js",
26
+ "cordis.patch.yml"
27
+ ],
28
+ "license": "MIT",
29
+ "dsh": {
30
+ "bundle": {
31
+ "patch": "./cordis.patch.yml"
32
+ },
33
+ "client": {
34
+ "platform": "web"
35
+ }
36
+ },
37
+ "peerDependencies": {
38
+ "@deepseek-ai/cordis": "^4.0.1",
39
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.7"
40
+ },
41
+ "dependencies": {
42
+ "zod": "^4.4.3"
43
+ },
44
+ "scripts": {
45
+ "patch:glyph": "node scripts/patch-glyph.mjs",
46
+ "check": "node --check index.js && node --check client.js && node --check typert.host.js && node --check scripts/patch-glyph.mjs"
47
+ }
48
+ }
package/typert.host.js ADDED
@@ -0,0 +1,361 @@
1
+ /**
2
+ * dsh-agent-approval — Typert Host manifest.
3
+ *
4
+ * Hand-written TYPERT manifest (the format the DSH typert-loader consumes from
5
+ * the package's `./typert` export). It describes the `agentApproval` Remote
6
+ * service the Host half publishes so the browser Client half can call it
7
+ * through `ctx.remote.agentApproval.*` (after mounting the namespace via
8
+ * `ctx.remote.$mount` — see client.js).
9
+ *
10
+ * Keep the invocation ids, service/namespace names and method names in sync
11
+ * with `index.js` (AgentApprovalService) and `client.js`.
12
+ *
13
+ * Result schemas are STRICT: every Host return value must match exactly
14
+ * (fields present, types correct), or the gateway validation fails.
15
+ */
16
+
17
+ import { z } from "zod";
18
+
19
+ // ---- shared shapes ---------------------------------------------------------
20
+
21
+ const recordSchema = z
22
+ .object({
23
+ at: z.string(),
24
+ sessionId: z.string(),
25
+ toolName: z.string(),
26
+ reason: z.string(),
27
+ args: z.string(),
28
+ outcome: z.enum(["allowed-once", "rejected", "cancelled", "unavailable"]),
29
+ riskLevel: z.string(),
30
+ model: z.string(),
31
+ durationMs: z.number(),
32
+ childSessionId: z.string(),
33
+ rationale: z.string(),
34
+ })
35
+ .readonly();
36
+
37
+ const modelRouteSchema = z
38
+ .object({
39
+ provider: z.string(),
40
+ model: z.string(),
41
+ })
42
+ .readonly();
43
+
44
+ const stateValueSchema = z
45
+ .object({
46
+ model: modelRouteSchema,
47
+ timeoutMs: z.number(),
48
+ enabledSessions: z.array(z.string()).readonly(),
49
+ records: z.array(recordSchema).readonly(),
50
+ })
51
+ .readonly();
52
+
53
+ const setModelValueSchema = z
54
+ .object({
55
+ model: modelRouteSchema,
56
+ })
57
+ .readonly();
58
+
59
+ const setTimeoutValueSchema = z
60
+ .object({
61
+ timeoutMs: z.number(),
62
+ })
63
+ .readonly();
64
+
65
+ const toggleValueSchema = z
66
+ .object({
67
+ message: z.string(),
68
+ })
69
+ .readonly();
70
+
71
+ const clearRecordsValueSchema = z
72
+ .object({
73
+ cleared: z.boolean(),
74
+ })
75
+ .readonly();
76
+
77
+ const directoryValueSchema = z
78
+ .object({
79
+ providers: z
80
+ .array(
81
+ z
82
+ .object({
83
+ id: z.string(),
84
+ name: z.string(),
85
+ })
86
+ .readonly(),
87
+ )
88
+ .readonly(),
89
+ models: z
90
+ .array(
91
+ z
92
+ .object({
93
+ provider: z.string(),
94
+ id: z.string(),
95
+ name: z.string(),
96
+ })
97
+ .readonly(),
98
+ )
99
+ .readonly(),
100
+ defaultSelection: z.union([modelRouteSchema, z.null()]),
101
+ })
102
+ .readonly();
103
+
104
+ /** The shared ok|error envelope. */
105
+ function okResult(valueSchema) {
106
+ return z.union([
107
+ z
108
+ .object({
109
+ ok: z.literal(true).readonly(),
110
+ value: valueSchema.readonly(),
111
+ })
112
+ .readonly(),
113
+ z
114
+ .object({
115
+ ok: z.literal(false).readonly(),
116
+ error: z
117
+ .object({
118
+ code: z.string().readonly(),
119
+ message: z.string().readonly().optional(),
120
+ })
121
+ .readonly(),
122
+ })
123
+ .readonly(),
124
+ ]);
125
+ }
126
+
127
+ const stateResultSchema = okResult(stateValueSchema);
128
+ const setModelResultSchema = okResult(setModelValueSchema);
129
+ const setTimeoutResultSchema = okResult(setTimeoutValueSchema);
130
+ const toggleResultSchema = okResult(toggleValueSchema);
131
+ const clearRecordsResultSchema = okResult(clearRecordsValueSchema);
132
+ const directoryResultSchema = okResult(directoryValueSchema);
133
+
134
+ // ---- per-invocation parameter schemas ----------------------------------------
135
+
136
+ const _agentApproval_setModel_parameter_0$schema = z.object({
137
+ provider: z.string(),
138
+ model: z.string(),
139
+ });
140
+
141
+ const _agentApproval_setApprovalTimeout_parameter_0$schema = z.object({
142
+ timeoutMs: z.number(),
143
+ });
144
+
145
+ const _agentApproval_toggle_parameter_0$schema = z.object({
146
+ sessionId: z.string(),
147
+ on: z.boolean(),
148
+ });
149
+
150
+ export const TYPERT = {
151
+ package: "dsh-agent-approval",
152
+ face: "host",
153
+ schemas: [],
154
+ invocations: [
155
+ {
156
+ id: "dsh-agent-approval#agentApproval/getState",
157
+ service: "agentApproval",
158
+ namespace: "agentApproval",
159
+ method: "getState",
160
+ invocation: { kind: "direct" },
161
+ parameters: [],
162
+ result: {
163
+ mode: "strict",
164
+ typeSymbol: "dsh-agent-approval#AgentApprovalStateResult",
165
+ schema: stateResultSchema,
166
+ },
167
+ sourceLocation: { file: "index.js", line: 1, column: 1 },
168
+ },
169
+ {
170
+ id: "dsh-agent-approval#agentApproval/setModel",
171
+ service: "agentApproval",
172
+ namespace: "agentApproval",
173
+ method: "setModel",
174
+ invocation: { kind: "direct" },
175
+ parameters: [
176
+ {
177
+ name: "request",
178
+ wire: "request",
179
+ source: "json",
180
+ codec: {
181
+ mode: "strict",
182
+ typeSymbol: "dsh-agent-approval#AgentApprovalSetModelRequest",
183
+ schema: _agentApproval_setModel_parameter_0$schema,
184
+ },
185
+ },
186
+ ],
187
+ result: {
188
+ mode: "strict",
189
+ typeSymbol: "dsh-agent-approval#AgentApprovalSetModelResult",
190
+ schema: setModelResultSchema,
191
+ },
192
+ sourceLocation: { file: "index.js", line: 1, column: 1 },
193
+ },
194
+ {
195
+ id: "dsh-agent-approval#agentApproval/setApprovalTimeout",
196
+ service: "agentApproval",
197
+ namespace: "agentApproval",
198
+ method: "setApprovalTimeout",
199
+ invocation: { kind: "direct" },
200
+ parameters: [
201
+ {
202
+ name: "request",
203
+ wire: "request",
204
+ source: "json",
205
+ codec: {
206
+ mode: "strict",
207
+ typeSymbol: "dsh-agent-approval#AgentApprovalSetTimeoutRequest",
208
+ schema: _agentApproval_setApprovalTimeout_parameter_0$schema,
209
+ },
210
+ },
211
+ ],
212
+ result: {
213
+ mode: "strict",
214
+ typeSymbol: "dsh-agent-approval#AgentApprovalSetTimeoutResult",
215
+ schema: setTimeoutResultSchema,
216
+ },
217
+ sourceLocation: { file: "index.js", line: 1, column: 1 },
218
+ },
219
+ {
220
+ id: "dsh-agent-approval#agentApproval/toggle",
221
+ service: "agentApproval",
222
+ namespace: "agentApproval",
223
+ method: "toggle",
224
+ invocation: { kind: "direct" },
225
+ parameters: [
226
+ {
227
+ name: "request",
228
+ wire: "request",
229
+ source: "json",
230
+ codec: {
231
+ mode: "strict",
232
+ typeSymbol: "dsh-agent-approval#AgentApprovalToggleRequest",
233
+ schema: _agentApproval_toggle_parameter_0$schema,
234
+ },
235
+ },
236
+ ],
237
+ result: {
238
+ mode: "strict",
239
+ typeSymbol: "dsh-agent-approval#AgentApprovalToggleResult",
240
+ schema: toggleResultSchema,
241
+ },
242
+ sourceLocation: { file: "index.js", line: 1, column: 1 },
243
+ },
244
+ {
245
+ id: "dsh-agent-approval#agentApproval/clearRecords",
246
+ service: "agentApproval",
247
+ namespace: "agentApproval",
248
+ method: "clearRecords",
249
+ invocation: { kind: "direct" },
250
+ parameters: [],
251
+ result: {
252
+ mode: "strict",
253
+ typeSymbol: "dsh-agent-approval#AgentApprovalClearRecordsResult",
254
+ schema: clearRecordsResultSchema,
255
+ },
256
+ sourceLocation: { file: "index.js", line: 1, column: 1 },
257
+ },
258
+ {
259
+ id: "dsh-agent-approval#agentApproval/directory",
260
+ service: "agentApproval",
261
+ namespace: "agentApproval",
262
+ method: "directory",
263
+ invocation: { kind: "direct" },
264
+ parameters: [],
265
+ result: {
266
+ mode: "strict",
267
+ typeSymbol: "dsh-agent-approval#AgentApprovalDirectoryResult",
268
+ schema: directoryResultSchema,
269
+ },
270
+ sourceLocation: { file: "index.js", line: 1, column: 1 },
271
+ },
272
+ ],
273
+ model: {
274
+ services: [
275
+ {
276
+ description:
277
+ "Agent-approval permission mode service: pins enabled sessions to a workspace-write base, judges every sandbox escalation with an independent approval subagent (fail closed), and exposes model config plus an audit log to the DeepSeek Harness web UI.",
278
+ summary: "Agent-approval permission mode service.",
279
+ tags: [],
280
+ jsDoc:
281
+ "/**\n * Agent-approval permission mode service: workspace-write base + independent approval agent.\n */",
282
+ key: "agentApproval",
283
+ exportName: "AgentApprovalService",
284
+ members: [
285
+ {
286
+ kind: "method",
287
+ name: "getState",
288
+ signature: "@Remote('getState') async getState(): Promise<AgentApprovalStateResult>",
289
+ summary: "Snapshot for the Settings page and the composer toggle.",
290
+ jsDoc:
291
+ "/**\n * Return the judge model route, timeout, enabled session ids, and the latest audit records.\n * @returns success or a business failure.\n */",
292
+ },
293
+ {
294
+ kind: "method",
295
+ name: "setModel",
296
+ signature: "@Remote('setModel') async setModel(request: AgentApprovalSetModelRequest): Promise<AgentApprovalSetModelResult>",
297
+ summary: "Set the judge model route (empty strings = inherit the requesting session's).",
298
+ jsDoc:
299
+ "/**\n * Set provider/model used by the approval subagent; empty strings clear the override.\n * @param request - { provider, model }.\n * @returns the stored route.\n */",
300
+ },
301
+ {
302
+ kind: "method",
303
+ name: "setApprovalTimeout",
304
+ signature: "@Remote('setApprovalTimeout') async setApprovalTimeout(request: AgentApprovalSetTimeoutRequest): Promise<AgentApprovalSetTimeoutResult>",
305
+ summary: "Set the judge timeout in ms (clamped 30000–600000; timeout resolves fail-closed).",
306
+ jsDoc:
307
+ "/**\n * Set the approval-agent timeout, clamped to [30000, 600000] milliseconds.\n * @param request - { timeoutMs }.\n * @returns the stored timeout.\n */",
308
+ },
309
+ {
310
+ kind: "method",
311
+ name: "toggle",
312
+ signature: "@Remote('toggle') async toggle(request: AgentApprovalToggleRequest): Promise<AgentApprovalToggleResult>",
313
+ summary: "Toggle the mode for one live session (pin/restore the permission knobs).",
314
+ jsDoc:
315
+ "/**\n * Enable (workspace-write base + judged escalations) or disable (restore prior knobs) for one live session.\n * @param request - { sessionId, on }.\n * @returns a human-readable message, or session-not-live.\n */",
316
+ },
317
+ {
318
+ kind: "method",
319
+ name: "clearRecords",
320
+ signature: "@Remote('clearRecords') async clearRecords(): Promise<AgentApprovalClearRecordsResult>",
321
+ summary: "Clear the in-memory audit records.",
322
+ jsDoc:
323
+ "/**\n * Clear the in-memory audit records.\n * @returns { cleared: true }.\n */",
324
+ },
325
+ {
326
+ kind: "method",
327
+ name: "directory",
328
+ signature: "@Remote('directory') async directory(): Promise<AgentApprovalDirectoryResult>",
329
+ summary: "Providers/models directory plus the harness default selection, for the Settings pickers.",
330
+ jsDoc:
331
+ "/**\n * List registered providers, their models, and the harness default selection.\n * @returns the picker directory.\n */",
332
+ },
333
+ ],
334
+ types: [
335
+ {
336
+ name: "AgentApprovalRecord",
337
+ declaration:
338
+ "export interface AgentApprovalRecord {\n readonly at: string;\n readonly sessionId: string;\n readonly toolName: string;\n readonly reason: string;\n readonly args: string;\n readonly outcome: 'allowed-once' | 'rejected' | 'cancelled' | 'unavailable';\n readonly riskLevel: string;\n readonly model: string;\n readonly durationMs: number;\n readonly childSessionId: string;\n readonly rationale: string;\n}",
339
+ },
340
+ {
341
+ name: "AgentApprovalModelRoute",
342
+ declaration:
343
+ "export interface AgentApprovalModelRoute {\n readonly provider: string;\n readonly model: string;\n}",
344
+ },
345
+ {
346
+ name: "AgentApprovalStateValue",
347
+ declaration:
348
+ "export interface AgentApprovalStateValue {\n readonly model: AgentApprovalModelRoute;\n readonly timeoutMs: number;\n readonly enabledSessions: readonly string[];\n readonly records: readonly AgentApprovalRecord[];\n}",
349
+ },
350
+ {
351
+ name: "AgentApprovalStateResult",
352
+ declaration:
353
+ "export type AgentApprovalStateResult = { ok: true; value: AgentApprovalStateValue } | { ok: false; error: { code: string; message?: string } };",
354
+ },
355
+ ],
356
+ },
357
+ ],
358
+ events: [],
359
+ objects: [],
360
+ },
361
+ };