@try-works/dsh-righthand 0.1.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.
@@ -0,0 +1,132 @@
1
+ /**
2
+ * secrets-tools — DSH-native tools over ctx.credentials + ctx.settings.
3
+ * The righthand plugin's auth surface: declare a credential reference,
4
+ * describe it (never the value), set/unset through the provider, and read
5
+ * plugin settings from a registered namespace (redacted).
6
+ * Built on the harness's own seams, not a hand-rolled secret store.
7
+ * @module dsh-righthand/secrets-tools
8
+ */
9
+ import { defineTool } from '@deepseek-ai/dsh-tools';
10
+ import { credentialRef } from '@deepseek-ai/dsh-credentials';
11
+ import { settingsNamespace } from '@deepseek-ai/dsh-settings';
12
+ import z from '@deepseek-ai/schemastery';
13
+ export const name = 'righthand-secrets';
14
+ export const inject = ['tools', 'credentials', 'settings'];
15
+ export const righthandSettingsSchema = z.object({
16
+ accountId: z.string().default(''),
17
+ defaultScriptPrefix: z.string().default('rh-'),
18
+ defaultZone: z.string().default(''),
19
+ });
20
+ /** Register the settings namespace + the secret/credential tools. */
21
+ export function apply(ctx) {
22
+ const ns = settingsNamespace('righthand');
23
+ // Register once; the settings provider merges schema defaults + user doc.
24
+ const scope = ctx.settings.register(ns, righthandSettingsSchema);
25
+ ctx.tools.register(defineTool({
26
+ name: 'rh_credential_describe',
27
+ description: 'Report whether a credential reference is configured (and from which source layer), without ever returning the secret value. Use this to check auth state before a deploy/invoke.',
28
+ parameters: {
29
+ ref: { type: 'string', required: true, description: 'Credential reference, e.g. CLOUDFLARE_API_TOKEN.' },
30
+ },
31
+ output: {
32
+ schema: {
33
+ type: 'object',
34
+ additionalProperties: false,
35
+ properties: {
36
+ ref: { type: 'string', required: true },
37
+ configured: { type: 'boolean', required: true },
38
+ source: { type: 'string' },
39
+ writable: { type: 'boolean', required: true },
40
+ },
41
+ },
42
+ render: (_args, value) => [{ type: 'text', text: `${value.ref}: configured=${value.configured}${value.source ? ' (source=' + value.source + ')' : ''}, writable=${value.writable}` }],
43
+ },
44
+ async execute(args) {
45
+ const ref = credentialRef(args.ref);
46
+ const info = await ctx.credentials.describe(ref);
47
+ return { ref: args.ref, configured: info.configured, ...info.source !== undefined ? { source: info.source } : {}, writable: info.writable };
48
+ },
49
+ }));
50
+ ctx.tools.register(defineTool({
51
+ name: 'rh_credential_set',
52
+ description: 'Store a secret value for a credential reference through the harness credential provider. The value is written durably and never echoed back.',
53
+ parameters: {
54
+ ref: { type: 'string', required: true, description: 'Credential reference to store, e.g. CLOUDFLARE_API_TOKEN.' },
55
+ value: { type: 'string', required: true, description: 'The non-empty secret value to store.' },
56
+ },
57
+ output: {
58
+ schema: {
59
+ type: 'object',
60
+ additionalProperties: false,
61
+ properties: {
62
+ ref: { type: 'string', required: true },
63
+ stored: { type: 'boolean', required: true },
64
+ },
65
+ },
66
+ render: (_args, value) => [{ type: 'text', text: `stored credential ${value.ref} (value not echoed)` }],
67
+ },
68
+ async execute(args) {
69
+ if (args.value.length === 0)
70
+ throw new Error('credential value must be non-empty');
71
+ await ctx.credentials.set(credentialRef(args.ref), args.value);
72
+ return { ref: args.ref, stored: true };
73
+ },
74
+ }));
75
+ ctx.tools.register(defineTool({
76
+ name: 'rh_credential_unset',
77
+ description: 'Remove a credential reference. Idempotent.',
78
+ parameters: {
79
+ ref: { type: 'string', required: true, description: 'Credential reference to remove.' },
80
+ },
81
+ output: {
82
+ schema: {
83
+ type: 'object',
84
+ additionalProperties: false,
85
+ properties: {
86
+ ref: { type: 'string', required: true },
87
+ removed: { type: 'boolean', required: true },
88
+ },
89
+ },
90
+ render: (_args, value) => [{ type: 'text', text: `removed credential ${value.ref}` }],
91
+ },
92
+ async execute(args) {
93
+ await ctx.credentials.unset(credentialRef(args.ref));
94
+ return { ref: args.ref, removed: true };
95
+ },
96
+ }));
97
+ ctx.tools.register(defineTool({
98
+ name: 'rh_settings_get',
99
+ description: 'Read the resolved righthand plugin settings (schema defaults + user overrides). Secret fields are redacted; use this to see effective account/zone/prefix configuration.',
100
+ parameters: {},
101
+ output: {
102
+ schema: { type: 'object', additionalProperties: true },
103
+ render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
104
+ },
105
+ async execute() {
106
+ const v = scope.get();
107
+ return { accountId: v.accountId, defaultScriptPrefix: v.defaultScriptPrefix, defaultZone: v.defaultZone };
108
+ },
109
+ }));
110
+ ctx.tools.register(defineTool({
111
+ name: 'rh_settings_set',
112
+ description: 'Merge a partial patch into the righthand settings namespace (persisted by the harness settings provider).',
113
+ parameters: {
114
+ patch: { type: 'object', additionalProperties: true, required: true, description: 'Partial settings patch, e.g. { "accountId": "..." }.' },
115
+ },
116
+ output: {
117
+ schema: {
118
+ type: 'object',
119
+ additionalProperties: false,
120
+ properties: {
121
+ applied: { type: 'boolean', required: true },
122
+ },
123
+ },
124
+ render: () => [{ type: 'text', text: 'settings updated' }],
125
+ },
126
+ async execute(args) {
127
+ await scope.update(args.patch);
128
+ return { applied: true };
129
+ },
130
+ }));
131
+ }
132
+ //# sourceMappingURL=secrets-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets-tools.js","sourceRoot":"","sources":["../src/secrets-tools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * store-tools — DSH-native tools over ctx.storageDomain (domain KV).
3
+ * A generic, schema-validated key-value store surface the righthand plugin
4
+ * can reuse for its tool catalog, plus any other plugin needing durable state.
5
+ * Built on the harness's own storage domain facility, not a hand-rolled file.
6
+ * @module dsh-righthand/store-tools
7
+ */
8
+ import type { Context } from '@deepseek-ai/cordis';
9
+ import type { Domain } from '@deepseek-ai/dsh-storage-domain';
10
+ import { z } from 'zod';
11
+ export declare const name = "righthand-store";
12
+ export declare const inject: string[];
13
+ /** A value that JSON.stringify can carry (objects, arrays, scalars — no functions/symbols). */
14
+ export type JsonLike = unknown;
15
+ /** One stored record: a string key → JSON value, with a write timestamp. */
16
+ export interface StoreRow {
17
+ value: JsonLike;
18
+ updatedAt: string;
19
+ }
20
+ /** Domain spec: the catalog table + a global singleton (write counter). */
21
+ export declare const storeDomain: {
22
+ name: string;
23
+ version: number;
24
+ tables: {
25
+ rows: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
26
+ value: unknown;
27
+ updatedAt: string;
28
+ }>;
29
+ };
30
+ global: {
31
+ schema: z.ZodObject<{
32
+ writes: z.ZodNumber;
33
+ }, z.core.$strip>;
34
+ initial: {
35
+ writes: number;
36
+ };
37
+ };
38
+ };
39
+ export type StoreDomain = Domain<typeof storeDomain>;
40
+ /** Persisted store with typed read/write over the domain tables. */
41
+ export declare class KeyValueStore {
42
+ private readonly domain;
43
+ constructor(domain: StoreDomain);
44
+ get(key: string): Promise<StoreRow | undefined>;
45
+ put(key: string, value: JsonLike): Promise<StoreRow>;
46
+ delete(key: string): Promise<boolean>;
47
+ list(): Promise<string[]>;
48
+ writes(): Promise<number>;
49
+ }
50
+ /** Open the store on the mounted domain facility and register the model-facing tools. */
51
+ export declare function apply(ctx: Context): void;
52
+ //# sourceMappingURL=store-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-tools.d.ts","sourceRoot":"","sources":["../src/store-tools.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAGlD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iCAAiC,CAAA;AAC7D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,IAAI,oBAAoB,CAAA;AACrC,eAAO,MAAM,MAAM,UAA6B,CAAA;AAEhD,+FAA+F;AAC/F,MAAM,MAAM,QAAQ,GAAG,OAAO,CAAA;AAE9B,4EAA4E;AAC5E,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,QAAQ,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,2EAA2E;AAC3E,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;CAatB,CAAA;AAEF,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,WAAW,CAAC,CAAA;AAEpD,oEAAoE;AACpE,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,WAAW;IAE1C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAI/C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAQpD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIrC,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIzB,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;CAGhC;AAED,yFAAyF;AACzF,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CA6GxC"}
@@ -0,0 +1,163 @@
1
+ /**
2
+ * store-tools — DSH-native tools over ctx.storageDomain (domain KV).
3
+ * A generic, schema-validated key-value store surface the righthand plugin
4
+ * can reuse for its tool catalog, plus any other plugin needing durable state.
5
+ * Built on the harness's own storage domain facility, not a hand-rolled file.
6
+ * @module dsh-righthand/store-tools
7
+ */
8
+ import { defineTool } from '@deepseek-ai/dsh-tools';
9
+ import { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain';
10
+ import { z } from 'zod';
11
+ export const name = 'righthand-store';
12
+ export const inject = ['tools', 'storageDomain'];
13
+ /** Domain spec: the catalog table + a global singleton (write counter). */
14
+ export const storeDomain = defineDomain({
15
+ name: 'righthand_store',
16
+ version: 1,
17
+ tables: {
18
+ rows: domainTable(z.object({
19
+ value: z.unknown(),
20
+ updatedAt: z.string(),
21
+ })),
22
+ },
23
+ global: {
24
+ schema: z.object({ writes: z.number() }),
25
+ initial: { writes: 0 },
26
+ },
27
+ });
28
+ /** Persisted store with typed read/write over the domain tables. */
29
+ export class KeyValueStore {
30
+ domain;
31
+ constructor(domain) {
32
+ this.domain = domain;
33
+ }
34
+ async get(key) {
35
+ return this.domain.table('rows').get(key);
36
+ }
37
+ async put(key, value) {
38
+ const row = { value, updatedAt: new Date().toISOString() };
39
+ await this.domain.table('rows').put(key, row);
40
+ const writes = this.domain.global.get();
41
+ await this.domain.global.set({ writes: writes.writes + 1 });
42
+ return row;
43
+ }
44
+ async delete(key) {
45
+ return this.domain.table('rows').delete(key);
46
+ }
47
+ async list() {
48
+ return [...this.domain.table('rows').keys()];
49
+ }
50
+ async writes() {
51
+ return this.domain.global.get().writes;
52
+ }
53
+ }
54
+ /** Open the store on the mounted domain facility and register the model-facing tools. */
55
+ export function apply(ctx) {
56
+ let store;
57
+ let ready;
58
+ // Open the domain asynchronously (the facility may still be activating).
59
+ ctx.effect(() => {
60
+ ready = (async () => {
61
+ const domain = await ctx.storageDomain.open(storeDomain);
62
+ store = new KeyValueStore(domain);
63
+ })();
64
+ return () => { };
65
+ });
66
+ const ensure = async () => {
67
+ if (store === undefined) {
68
+ await ready;
69
+ }
70
+ if (store === undefined)
71
+ throw new Error('righthand store is not open (storageDomain not mounted)');
72
+ return store;
73
+ };
74
+ ctx.tools.register(defineTool({
75
+ name: 'rh_store_put',
76
+ description: 'Durably store a JSON value under a string key in the righthand KV store. Returns the stored record with a timestamp.',
77
+ parameters: {
78
+ key: { type: 'string', required: true, description: 'String key for the record.' },
79
+ value: { type: 'json', required: true, description: 'Any JSON value to store (object, array, string, number, boolean, null).' },
80
+ },
81
+ output: {
82
+ schema: {
83
+ type: 'object',
84
+ additionalProperties: false,
85
+ properties: {
86
+ key: { type: 'string', required: true },
87
+ updatedAt: { type: 'string', required: true },
88
+ writes: { type: 'integer', required: true },
89
+ },
90
+ },
91
+ render: (_args, value) => [{ type: 'text', text: `stored ${value.key} (writes=${value.writes})` }],
92
+ },
93
+ async execute(args) {
94
+ const s = await ensure();
95
+ const row = await s.put(args.key, args.value);
96
+ return { key: args.key, updatedAt: row.updatedAt, writes: await s.writes() };
97
+ },
98
+ }));
99
+ ctx.tools.register(defineTool({
100
+ name: 'rh_store_get',
101
+ description: 'Read a stored JSON value by key. Returns null when the key is absent.',
102
+ parameters: {
103
+ key: { type: 'string', required: true, description: 'String key to read.' },
104
+ },
105
+ output: {
106
+ schema: {
107
+ type: 'object',
108
+ additionalProperties: false,
109
+ properties: {
110
+ found: { type: 'boolean', required: true },
111
+ key: { type: 'string', required: true },
112
+ value: { type: 'json' },
113
+ updatedAt: { type: 'string' },
114
+ },
115
+ },
116
+ render: (_args, value) => [{ type: 'text', text: value.found ? `${value.key} = ${JSON.stringify(value.value)}` : `${value.key} (absent)` }],
117
+ },
118
+ async execute(args) {
119
+ const s = await ensure();
120
+ const row = await s.get(args.key);
121
+ if (row === undefined)
122
+ return { found: false, key: args.key };
123
+ return { found: true, key: args.key, value: row.value, updatedAt: row.updatedAt };
124
+ },
125
+ }));
126
+ ctx.tools.register(defineTool({
127
+ name: 'rh_store_delete',
128
+ description: 'Delete a stored record by key. Returns whether it existed.',
129
+ parameters: {
130
+ key: { type: 'string', required: true, description: 'String key to delete.' },
131
+ },
132
+ output: {
133
+ schema: {
134
+ type: 'object',
135
+ additionalProperties: false,
136
+ properties: {
137
+ key: { type: 'string', required: true },
138
+ existed: { type: 'boolean', required: true },
139
+ },
140
+ },
141
+ render: (_args, value) => [{ type: 'text', text: `deleted ${value.key}: ${value.existed}` }],
142
+ },
143
+ async execute(args) {
144
+ const s = await ensure();
145
+ const existed = await s.delete(args.key);
146
+ return { key: args.key, existed };
147
+ },
148
+ }));
149
+ ctx.tools.register(defineTool({
150
+ name: 'rh_store_list',
151
+ description: 'List all keys currently in the righthand KV store.',
152
+ parameters: {},
153
+ output: {
154
+ schema: { type: 'array', items: { type: 'string' } },
155
+ render: (_args, keys) => [{ type: 'text', text: keys.length === 0 ? '(empty)' : keys.join('\n') }],
156
+ },
157
+ async execute() {
158
+ const s = await ensure();
159
+ return s.list();
160
+ },
161
+ }));
162
+ }
163
+ //# sourceMappingURL=store-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-tools.js","sourceRoot":"","sources":["../src/store-tools.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAA;AAE3E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,IAAI,GAAG,iBAAiB,CAAA;AACrC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,eAAe,CAAC,CAAA;AAWhD,2EAA2E;AAC3E,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC;IACtC,IAAI,EAAE,iBAAiB;IACvB,OAAO,EAAE,CAAC;IACV,MAAM,EAAE;QACN,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC;YACzB,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE;YAClB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;SACtB,CAAC,CAAC;KACJ;IACD,MAAM,EAAE;QACN,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;QACxC,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE;KACvB;CACF,CAAC,CAAA;AAIF,oEAAoE;AACpE,MAAM,OAAO,aAAa;IACK;IAA7B,YAA6B,MAAmB;QAAnB,WAAM,GAAN,MAAM,CAAa;IAAG,CAAC;IAEpD,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAyB,CAAA;IACnE,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAe;QACpC,MAAM,GAAG,GAAa,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAA;QACpE,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;QAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAA;QACvC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAA;QAC3D,OAAO,GAAG,CAAA;IACZ,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAC9C,CAAC;IAED,KAAK,CAAC,IAAI;QACR,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;IAC9C,CAAC;IAED,KAAK,CAAC,MAAM;QACV,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAA;IACxC,CAAC;CACF;AAED,yFAAyF;AACzF,MAAM,UAAU,KAAK,CAAC,GAAY;IAChC,IAAI,KAAgC,CAAA;IACpC,IAAI,KAAgC,CAAA;IAEpC,yEAAyE;IACzE,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE;QACd,KAAK,GAAG,CAAC,KAAK,IAAI,EAAE;YAClB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;YACxD,KAAK,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,CAAA;QACnC,CAAC,CAAC,EAAE,CAAA;QACJ,OAAO,GAAG,EAAE,GAAqE,CAAC,CAAA;IACpF,CAAC,CAAC,CAAA;IAEF,MAAM,MAAM,GAAG,KAAK,IAA4B,EAAE;QAChD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YAAC,MAAM,KAAK,CAAC;QAAC,CAAC;QACzC,IAAI,KAAK,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAA;QACnG,OAAO,KAAK,CAAA;IACd,CAAC,CAAA;IAED,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC5B,IAAI,EAAE,cAAc;QACpB,WAAW,EAAE,sHAAsH;QACnI,UAAU,EAAE;YACV,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,4BAA4B,EAAE;YAClF,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,yEAAyE,EAAE;SAChI;QACD,MAAM,EAAE;YACN,MAAM,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,oBAAoB,EAAE,KAAK;gBAC3B,UAAU,EAAE;oBACV,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;oBACvC,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;oBAC7C,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE;iBAC5C;aACF;YACD,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC;SACnG;QACD,KAAK,CAAC,OAAO,CAAC,IAAI;YAChB,MAAM,CAAC,GAAG,MAAM,MAAM,EAAE,CAAA;YACxB,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;YAC7C,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAA;QAC9E,CAAC;KACF,CAAC,CAAC,CAAA;IAEH,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC5B,IAAI,EAAE,cAAc;QACpB,WAAW,EAAE,uEAAuE;QACpF,UAAU,EAAE;YACV,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,qBAAqB,EAAE;SAC5E;QACD,MAAM,EAAE;YACN,MAAM,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,oBAAoB,EAAE,KAAK;gBAC3B,UAAU,EAAE;oBACV,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE;oBAC1C,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;oBACvC,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;oBACvB,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;iBAC9B;aACF;YACD,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,WAAW,EAAE,CAAC;SAC5I;QACD,KAAK,CAAC,OAAO,CAAC,IAAI;YAChB,MAAM,CAAC,GAAG,MAAM,MAAM,EAAE,CAAA;YACxB,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YACjC,IAAI,GAAG,KAAK,SAAS;gBAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAA;YAC7D,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,KAAY,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAA;QAC1F,CAAC;KACF,CAAC,CAAC,CAAA;IAEH,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC5B,IAAI,EAAE,iBAAiB;QACvB,WAAW,EAAE,4DAA4D;QACzE,UAAU,EAAE;YACV,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uBAAuB,EAAE;SAC9E;QACD,MAAM,EAAE;YACN,MAAM,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,oBAAoB,EAAE,KAAK;gBAC3B,UAAU,EAAE;oBACV,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;oBACvC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE;iBAC7C;aACF;YACD,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;SAC7F;QACD,KAAK,CAAC,OAAO,CAAC,IAAI;YAChB,MAAM,CAAC,GAAG,MAAM,MAAM,EAAE,CAAA;YACxB,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YACxC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,CAAA;QACnC,CAAC;KACF,CAAC,CAAC,CAAA;IAEH,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC5B,IAAI,EAAE,eAAe;QACrB,WAAW,EAAE,oDAAoD;QACjE,UAAU,EAAE,EAAE;QACd,MAAM,EAAE;YACN,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;YACpD,MAAM,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;SACnG;QACD,KAAK,CAAC,OAAO;YACX,MAAM,CAAC,GAAG,MAAM,MAAM,EAAE,CAAA;YACxB,OAAO,CAAC,CAAC,IAAI,EAAE,CAAA;QACjB,CAAC;KACF,CAAC,CAAC,CAAA;AACL,CAAC"}
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@try-works/dsh-righthand",
3
+ "description": "DeepSeek Harness plugin: DSH-native righthand tools — durable KV store, credential/settings management, governed exec, and a tool guard",
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./src/*": "./src/*",
14
+ "./cordis.patch.yml": "./cordis.patch.yml",
15
+ "./package.json": "./package.json"
16
+ },
17
+ "scripts": {
18
+ "build": "tsc -p tsconfig.build.json",
19
+ "test": "vitest run",
20
+ "typecheck": "tsc --noEmit",
21
+ "prepack": "npm run build"
22
+ },
23
+ "peerDependencies": {
24
+ "@deepseek-ai/cordis": "^4.0.1",
25
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.5",
26
+ "@deepseek-ai/dsh-storage-domain": "^0.1.0-rc.5",
27
+ "@deepseek-ai/dsh-credentials": "^0.1.0-rc.5",
28
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.5",
29
+ "@deepseek-ai/dsh-subprocess": "^0.1.0-rc.5",
30
+ "@deepseek-ai/dsh-jobs": "^0.1.0-rc.5",
31
+ "@deepseek-ai/schemastery": "^3.18.1"
32
+ },
33
+ "devDependencies": {
34
+ "@deepseek-ai/cordis": "file:D:/deepseek-harness/vendor/cordis",
35
+ "@deepseek-ai/cosmokit": "file:D:/deepseek-harness/vendor/cosmokit",
36
+ "@deepseek-ai/schemastery": "file:D:/deepseek-harness/vendor/schemastery",
37
+ "@deepseek-ai/dsh-tools": "file:D:/deepseek-harness/packages/core/tools",
38
+ "@deepseek-ai/dsh-system-prompt": "file:D:/deepseek-harness/packages/core/system-prompt",
39
+ "@deepseek-ai/dsh-storage": "file:D:/deepseek-harness/packages/storage/storage",
40
+ "@deepseek-ai/dsh-storage-json": "file:D:/deepseek-harness/packages/storage/storage-json",
41
+ "@deepseek-ai/dsh-storage-domain": "file:D:/deepseek-harness/packages/storage/storage-domain",
42
+ "@deepseek-ai/dsh-credentials": "file:D:/deepseek-harness/packages/credentials/credentials",
43
+ "@deepseek-ai/dsh-settings": "file:D:/deepseek-harness/packages/settings/settings",
44
+ "@deepseek-ai/dsh-jobs": "file:D:/deepseek-harness/packages/jobs/jobs",
45
+ "@deepseek-ai/dsh-jobs-local": "file:D:/deepseek-harness/packages/jobs/jobs-local",
46
+ "@deepseek-ai/dsh-subprocess": "file:D:/deepseek-harness/packages/subprocess/subprocess",
47
+ "@deepseek-ai/dsh-subprocess-local": "file:D:/deepseek-harness/packages/subprocess/subprocess-local",
48
+ "@deepseek-ai/dsh-llm": "file:D:/deepseek-harness/packages/llm/llm",
49
+ "@deepseek-ai/dsh-agent": "file:D:/deepseek-harness/packages/core/agent",
50
+ "@deepseek-ai/dsh-scope": "file:D:/deepseek-harness/packages/core/scope",
51
+ "@deepseek-ai/dsh-session": "file:D:/deepseek-harness/packages/core/session",
52
+ "@deepseek-ai/dsh-invariants": "file:D:/deepseek-harness/packages/runtime-diagnostics/invariants",
53
+ "@deepseek-ai/dsh-user-approval": "file:D:/deepseek-harness/packages/interaction/user-approval",
54
+ "@deepseek-ai/dsh-brand": "file:D:/deepseek-harness/packages/util/brand",
55
+ "@deepseek-ai/dsh-timeout": "file:D:/deepseek-harness/packages/util/timeout",
56
+ "@deepseek-ai/dsh-typert-protocol": "file:D:/deepseek-harness/packages/typert/protocol",
57
+ "typescript": "^5.6.0",
58
+ "tsx": "^4.19.0",
59
+ "vitest": "^2.1.0",
60
+ "@types/node": "^20.0.0",
61
+ "zod": "^4.4.3",
62
+ "@deepseek-ai/dsh-credentials-local": "file:D:/deepseek-harness/packages/credentials/credentials-local",
63
+ "@deepseek-ai/dsh-settings-file": "file:D:/deepseek-harness/packages/settings/settings-file",
64
+ "@deepseek-ai/dsh-atomic-write": "file:D:/deepseek-harness/packages/util/atomic-write",
65
+ "@deepseek-ai/dsh-home-paths": "file:D:/deepseek-harness/packages/util/home-paths",
66
+ "@deepseek-ai/dsh-launch-environment": "file:D:/deepseek-harness/packages/util/launch-environment",
67
+ "@deepseek-ai/dsh-code-runtime": "file:D:/deepseek-harness/packages/code-runtime/code-runtime",
68
+ "@deepseek-ai/dsh-attachment": "file:D:/deepseek-harness/packages/attachment/attachment",
69
+ "@deepseek-ai/dsh-commands": "file:D:/deepseek-harness/packages/interaction/commands",
70
+ "@deepseek-ai/dsh-skill": "file:D:/deepseek-harness/packages/skill/skill"
71
+ },
72
+ "files": [
73
+ "lib",
74
+ "src",
75
+ "cordis.patch.yml",
76
+ "README.md",
77
+ "LICENSE"
78
+ ],
79
+ "dsh": {
80
+ "bundle": {
81
+ "patch": "./cordis.patch.yml"
82
+ }
83
+ },
84
+ "repository": {
85
+ "type": "git",
86
+ "url": "git+https://github.com/try-works/dsh-righthand.git"
87
+ },
88
+ "publishConfig": {
89
+ "access": "public"
90
+ },
91
+ "dependencies": {
92
+ "zod": "^4.4.3"
93
+ }
94
+ }
@@ -0,0 +1,39 @@
1
+ # DSH-native tools — learnings log
2
+
3
+ > Self-built tools on the harness's OWN services, verified by mounting the real providers and invoking each tool's `execute()` path. Not read-verified — executed.
4
+
5
+ ## What I built (all in `src/`)
6
+
7
+ | Module | Tools | Native service it wraps |
8
+ |---|---|---|
9
+ | `store-tools.ts` | `rh_store_put/get/delete/list` | `ctx.storageDomain` (domain KV: table + global counter) |
10
+ | `secrets-tools.ts` | `rh_credential_describe/set/unset`, `rh_settings_get/set` | `ctx.credentials` + `ctx.settings` |
11
+ | `exec-tools.ts` | `rh_run`, `rh_run_bg` | `ctx.subprocess` + `ctx.jobs` |
12
+ | `guard-tools.ts` | (policy, not a tool) | `ctx.tools` `tools/pre-execute` |
13
+
14
+ ## Test results (mine — `tests/dsh-native-tools.spec.ts`, 7/7 pass)
15
+
16
+ Booted a real `Context` with: `SystemPrompt` + `ToolRuntime` + `Storage` hub + `storage-json` + `storage-domain` + `credentials-local` + `settings-file` + `jobs-local` + `subprocess-local`, then mounted each tool plugin and called `ctx.tools.execute(...)`.
17
+
18
+ 1. `rh_store_put/get/delete/list` round-trips a JSON value durably through the domain KV.
19
+ 2. The domain's **global singleton** increments a write counter across puts (writes=1 then writes=2).
20
+ 3. `rh_credential_set` stores a secret; `rh_credential_describe` reports `configured` + source WITHOUT the value (asserted the secret string never appears in any output).
21
+ 4. `rh_settings_get` returns schema defaults (`rh-` prefix); `rh_settings_set` merges a patch and re-reads it.
22
+ 5. `rh_run` runs `node --version` → exit 0 + stdout matching `/v\d+/`.
23
+ 6. `rh_run_bg` starts a `ctx.jobs` background job that settles `completed`.
24
+ 7. `guard-tools` denies a `rh_deny_` tool through `tools/pre-execute` (result `isError: true`).
25
+
26
+ ## Learnings during build (mine — where the harness corrected my notes)
27
+
28
+ 1. **The real `ctx.storage` is a storage HUB, not `ctx.storage.sql`.** My §9.2 notes said `ctx.storage.sql` (that is the Cloudflare DO's storage, not the harness). The harness `ctx.storage` exposes named backends (`backend.register('json', ...)`) with a `kv` facet; the ergonomic layer is `ctx.storageDomain` → `defineDomain` + `domain.table('rows').put/get/delete/keys()` + `domain.global.get()/set()`. Records are validated by **zod** (not schemastery) at the durable-read boundary.
29
+ 2. **`domain.table(name)` returns a handle; there is no `domain.tables.<name>` object.** And the table verb is `put`, not `set`. My first pass guessed the API and the execute() returned `Cannot read properties of undefined (reading 'set')` — the real API corrected it.
30
+ 3. **`ctx.jobs.start` refuses until a controller is attached** (`attachController('name')`), exactly like `tool-jobs`. My first `rh_run_bg` would have been rejected without it.
31
+ 4. **`ctx.plugin()` accepts function/class/`{apply}` plugins uniformly**, and `Service` subclasses (`LocalCredentialProvider`, `FileSettingsProvider`, `LocalJobRegistry`, `Storage`, `ToolRuntime`, `SystemPrompt`) are directly pluggable — the `file:` devDeps + pnpm `overrides`/`linkWorkspacePackages` recipe (from `dsh-paper-design`) is the right way to test against the real runtime.
32
+ 5. **`tools/pre-execute` with `{prepend: true}` + a throw is the clean deny gate** — the tool never dispatches and the result is `isError`. No wrapper needed.
33
+ 6. **Cordis Contexts cannot have arbitrary properties set on them** (`cannot set property without provide`). Observability must go through a real service/`provide`, not a monkey-patched field.
34
+ 7. **Subprocess env scrubbing is built in**: the seam exports `scrubbedParentEnv()` (strips `KEY/PASSWORD/SECRET/TOKEN` + `DSH_*`), so a plugin spawning a CLI gets secret hygiene for free.
35
+
36
+ ## Fits dsh-righthand as
37
+
38
+ - These are the **generic DSH-native primitives** the Cloudflare tools will build on: the durable tool catalog is `rh_store_*` over `storageDomain`; auth is `rh_credential_*`; deploy/invoke is `rh_run`/`rh_run_bg` over subprocess+jobs; high-impact gates are `guard-tools`.
39
+ - The **test harness** (`tests/dsh-native-tools.spec.ts`) is the reusable mount pattern for every future tool module.
@@ -0,0 +1,121 @@
1
+ /**
2
+ * exec-tools — DSH-native tools over ctx.subprocess + ctx.jobs.
3
+ * A governed "run a command" surface: rh_run (foreground collect) and
4
+ * rh_run_bg (background job via ctx.jobs). Built on the harness's own
5
+ * subprocess and job services, so cancellation + tree-scoped termination
6
+ * + ownership come from the harness, not a hand-rolled child_process.
7
+ * @module dsh-righthand/exec-tools
8
+ */
9
+
10
+ import type { Context } from '@deepseek-ai/cordis'
11
+ import { defineTool } from '@deepseek-ai/dsh-tools'
12
+ import type { SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess'
13
+ import type {} from '@deepseek-ai/dsh-jobs'
14
+
15
+ export const name = 'righthand-exec'
16
+ export const inject = ['tools', 'subprocess', 'jobs']
17
+
18
+ /** Run a command and collect bounded output; returns exit facts + tail. */
19
+ export function apply(ctx: Context): void {
20
+ // Producers may start work only while a controller is attached (tool-jobs pattern).
21
+ ctx.jobs.attachController('righthand-exec')
22
+ ctx.tools.register(defineTool({
23
+ name: 'rh_run',
24
+ description: 'Run a single command (argv array, no shell interpretation) in collect mode and return exit facts plus bounded stdout/stderr tail. Cancellation is tree-scoped by the harness subprocess service.',
25
+ parameters: {
26
+ argv: { type: 'array', required: true, items: { type: 'string' }, description: 'Full argv; argv[0] is the executable (e.g. ["node","--version"]).' },
27
+ cwd: { type: 'string', description: 'Working directory (defaults to process.cwd()).' },
28
+ maxOutputBytes: { type: 'integer', description: 'Per-stream in-memory cap (default 4096).' },
29
+ },
30
+ output: {
31
+ schema: {
32
+ type: 'object',
33
+ additionalProperties: false,
34
+ properties: {
35
+ exitCode: { type: 'number' },
36
+ signal: { type: 'string' },
37
+ stdout: { type: 'string', required: true },
38
+ stderr: { type: 'string', required: true },
39
+ stdoutTruncated: { type: 'boolean', required: true },
40
+ stderrTruncated: { type: 'boolean', required: true },
41
+ },
42
+ },
43
+ render: (_args, value) => [{ type: 'text', text: `exit ${value.exitCode ?? ('signal ' + value.signal)}; stdout=${value.stdout.length}b stderr=${value.stderr.length}b` }],
44
+ },
45
+ async execute(args, exec) {
46
+ const maxBytes = args.maxOutputBytes ?? 4096
47
+ const spec: SubprocessSpawnSpec = {
48
+ argv: args.argv,
49
+ cwd: args.cwd ?? process.cwd(),
50
+ stdio: {
51
+ stdin: 'ignore',
52
+ stdout: { maxBytes },
53
+ stderr: { maxBytes },
54
+ },
55
+ graceMs: 5000,
56
+ signal: exec.signal,
57
+ }
58
+ const handle = ctx.subprocess.spawn(spec)
59
+ const outcome = await handle.done
60
+ const so = handle.collected.stdout?.readFrom(0)
61
+ const se = handle.collected.stderr?.readFrom(0)
62
+ return {
63
+ exitCode: outcome.exitCode ?? undefined,
64
+ ...outcome.signal !== null ? { signal: String(outcome.signal) } : {},
65
+ stdout: so?.text ?? '',
66
+ stderr: se?.text ?? '',
67
+ stdoutTruncated: so?.lossy ?? false,
68
+ stderrTruncated: se?.lossy ?? false,
69
+ }
70
+ },
71
+ }))
72
+
73
+ ctx.tools.register(defineTool({
74
+ name: 'rh_run_bg',
75
+ description: 'Start a command as a background job (owner-scoped, cancellable via the harness job registry). Returns the job id; read it back with the job_output-style flow or the registry.',
76
+ parameters: {
77
+ argv: { type: 'array', required: true, items: { type: 'string' }, description: 'Full argv; argv[0] is the executable.' },
78
+ cwd: { type: 'string', description: 'Working directory (defaults to process.cwd()).' },
79
+ label: { type: 'string', description: 'Short human-readable label for the job.' },
80
+ },
81
+ output: {
82
+ schema: {
83
+ type: 'object',
84
+ additionalProperties: false,
85
+ properties: {
86
+ jobId: { type: 'string', required: true },
87
+ label: { type: 'string', required: true },
88
+ },
89
+ },
90
+ render: (_args, value) => [{ type: 'text', text: `started background job ${value.jobId} (${value.label})` }],
91
+ },
92
+ async execute(args, exec) {
93
+ const label = args.label ?? args.argv.join(' ')
94
+ const jobId = ctx.jobs.start({
95
+ kind: 'bash' as any,
96
+ label,
97
+ owner: exec.agent,
98
+ run() {
99
+ const handle = ctx.subprocess.spawn({
100
+ argv: args.argv,
101
+ cwd: args.cwd ?? process.cwd(),
102
+ stdio: { stdin: 'ignore', stdout: { maxBytes: 8192 }, stderr: { maxBytes: 8192 } },
103
+ graceMs: 5000,
104
+ })
105
+ return {
106
+ cancel(reason) { handle.terminate() },
107
+ done: (async () => {
108
+ const outcome = await handle.done
109
+ const so = handle.collected.stdout?.readFrom(0)
110
+ const se = handle.collected.stderr?.readFrom(0)
111
+ if (outcome.exitCode === 0) return { status: 'completed' as const, detail: `exit 0`, output: so?.text ?? '' }
112
+ return { status: 'failed' as const, detail: `exit ${outcome.exitCode}`, output: (se?.text ?? '') || (so?.text ?? '') }
113
+ })(),
114
+ readOutput() { return '' },
115
+ }
116
+ },
117
+ })
118
+ return { jobId: String(jobId), label }
119
+ },
120
+ }))
121
+ }