@try-works/dsh-righthand 0.1.1 → 0.1.3
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 +76 -0
- package/lib/guard-tools.d.ts +29 -0
- package/lib/guard-tools.d.ts.map +1 -1
- package/lib/guard-tools.js +21 -9
- package/lib/guard-tools.js.map +1 -1
- package/lib/index.d.ts +9 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +9 -3
- package/lib/index.js.map +1 -1
- package/lib/skills.d.ts +23 -0
- package/lib/skills.d.ts.map +1 -0
- package/lib/skills.js +37 -0
- package/lib/skills.js.map +1 -0
- package/package.json +4 -2
- package/skills/dsh-righthand/SKILL.md +49 -0
- package/src/guard-tools.ts +41 -9
- package/src/index.ts +10 -3
- package/src/skills.ts +47 -0
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@ tool guard. Every tool is built on the harness's own services
|
|
|
6
6
|
(`storageDomain`, `credentials`, `settings`, `subprocess`, `jobs`, `tools`),
|
|
7
7
|
not hand-rolled primitives.
|
|
8
8
|
|
|
9
|
+
The plugin also ships **blueprint guidance** — ten named recipes for the agent-built tools this plugin exists to compose (see [Blueprints](#blueprints) below) — plus a packaged skill, a settings namespace, and an optional tool guard.
|
|
10
|
+
|
|
9
11
|
## Install
|
|
10
12
|
|
|
11
13
|
```bash
|
|
@@ -46,6 +48,13 @@ dsh --profile <profile> --patch ./cordis.patch.yml
|
|
|
46
48
|
Guard modes: `allow` (pass through), `deny` (block with an error), `ask`
|
|
47
49
|
(defer to the policy function). Omitted rules leave the guard inert.
|
|
48
50
|
|
|
51
|
+
Rules may also declare `destructive: true` — a documentation flag for
|
|
52
|
+
irreversible effects (deploy, delete, DNS change). It does not change
|
|
53
|
+
enforcement; it records *why* the prefix is gated, and
|
|
54
|
+
`tests/permissions.golden` records every `rh_*` tool's derived guard facts so
|
|
55
|
+
guard changes are never silent (run `UPDATE_GOLDEN=1 pnpm test` to
|
|
56
|
+
re-baseline deliberately).
|
|
57
|
+
|
|
49
58
|
## Settings
|
|
50
59
|
|
|
51
60
|
Namespace "righthand" (registered by the secrets family):
|
|
@@ -59,6 +68,73 @@ Namespace "righthand" (registered by the secrets family):
|
|
|
59
68
|
`rh_settings_get` returns the resolved values; `rh_settings_set` merges a
|
|
60
69
|
partial patch (persisted by the harness settings provider).
|
|
61
70
|
|
|
71
|
+
## Blueprints
|
|
72
|
+
|
|
73
|
+
Blueprint guidance lives under `experiments/<blueprint>/` — each a
|
|
74
|
+
`blueprint.json` spec plus a runnable template kit (`run.ts` + `test.ts`,
|
|
75
|
+
`LEARNINGS.md`, and for the cloud-tested ones a `cloud/` Worker). They
|
|
76
|
+
describe the tools this plugin composes; the daily-digest kit is the
|
|
77
|
+
canonical example of the current DSH-native form (tool-to-tool via
|
|
78
|
+
`ctx.tools.execute`, synthesis via `ctx.llm`, deliverables via `ctx.fs`).
|
|
79
|
+
|
|
80
|
+
| Blueprint | What it builds |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `blueprint/daily-digest` | Collect sources, summarize, send on a schedule — extractive locally, LLM synthesis as escalation |
|
|
83
|
+
| `blueprint/research-radar` | Multi-source research over a rolling window (Reddit, X, YouTube, HN, Polymarket, web): score, dedupe, synthesize with citations |
|
|
84
|
+
| `blueprint/web-scraper` | Fetch a page, extract title/headings/links/text, make it searchable — static HTML first, render/vision as escalation |
|
|
85
|
+
| `blueprint/vision-worker` | Cloudflare Worker exposing kimi-k2.6 vision: route image requests for text-only models, return a versioned `VisionEnvelope` |
|
|
86
|
+
| `blueprint/vision-qa-assistant` | Answer questions about images for a text-only model — hash-cache locally, Workers AI vision as the semantic path |
|
|
87
|
+
| `blueprint/inbound-webhook-pipeline` | Receive → validate → run job → store → notify, with idempotency-key dedupe as the load-bearing invariant |
|
|
88
|
+
| `blueprint/event-sourced-store` | Tool store where every mutation is an event: govern, checksum, append offset-addressable events, catch up by version |
|
|
89
|
+
| `blueprint/governed-registry` | Register a tool format; every write runs the governed flow: validate → normalize → checksum → index → summarize |
|
|
90
|
+
| `blueprint/mcp-tool-surface` | Deterministic + AI-backed tools behind a stateless MCP surface with normalized LLM output |
|
|
91
|
+
| `blueprint/session-watcher` | Poll DSH sessions, evaluate a condition (new publish / builder idle / cadence), message a target session |
|
|
92
|
+
|
|
93
|
+
Analyses of the [micro.mu](https://github.com/micro/mu) project this plugin
|
|
94
|
+
learns from: [`docs/agent-tool-catalog.md`](docs/agent-tool-catalog.md) (tool
|
|
95
|
+
families worth adding, from the tools catalogue + permission commit) and
|
|
96
|
+
[`docs/mu-repo-analysis.md`](docs/mu-repo-analysis.md) (architecture lessons
|
|
97
|
+
from the whole repo — single-source specs, golden permissions, layering,
|
|
98
|
+
held-state gates, SSRF guarding).
|
|
99
|
+
|
|
100
|
+
## Packaged skill
|
|
101
|
+
|
|
102
|
+
The plugin ships the `dsh-righthand` skill
|
|
103
|
+
(`skills/dsh-righthand/SKILL.md`, the dsh-plugin packaged-skill standard):
|
|
104
|
+
tool-reference tables, store/credential/settings semantics, guard modes, and
|
|
105
|
+
service availability. It registers via `ctx.skills` on mount, so the agent's
|
|
106
|
+
skill catalog lists it automatically.
|
|
107
|
+
|
|
108
|
+
## Use cases
|
|
109
|
+
|
|
110
|
+
Everything below runs through the harness's own services; nothing is
|
|
111
|
+
hand-rolled.
|
|
112
|
+
|
|
113
|
+
**Durable agent memory / task board.** Store task cards under `task:<slug>`
|
|
114
|
+
and let `rh_store_list` enumerate them across restarts and context
|
|
115
|
+
compaction — no project files touched. Complete by overwriting, clean up
|
|
116
|
+
with `rh_store_delete`.
|
|
117
|
+
|
|
118
|
+
**Pre-deploy gate.** Before any deploy: `rh_credential_describe` asserts the
|
|
119
|
+
API token exists, `rh_settings_get` asserts the account/zone, `rh_run` runs
|
|
120
|
+
tests + build, `rh_store_put` records a deploy receipt, and a guard rule
|
|
121
|
+
gates the actual deploy tool behind `mode: 'ask'`.
|
|
122
|
+
|
|
123
|
+
**Secret onboarding.** `rh_credential_set` stores a key through the
|
|
124
|
+
credential provider (the value is never echoed back); `rh_credential_describe`
|
|
125
|
+
reports configured state + source; rotation is unset-then-set.
|
|
126
|
+
|
|
127
|
+
**Daily digests.** The agent fetches sources and stores them under
|
|
128
|
+
`digest:<date>` (see `blueprint/daily-digest`), diffing days with
|
|
129
|
+
`rh_store_get` and rendering MDX deliverables through `ctx.fs`.
|
|
130
|
+
|
|
131
|
+
**CI-lite.** `rh_run_bg` starts a long build as an owner-scoped background
|
|
132
|
+
job; its id + status live in the store so a later turn can poll and collect.
|
|
133
|
+
|
|
134
|
+
**Governed exec.** Any high-impact command can be fronted by a guard rule
|
|
135
|
+
(`deny` to block a prefix, `ask` to require a policy function's approval),
|
|
136
|
+
so the agent cannot deploy or destroy without the gate.
|
|
137
|
+
|
|
62
138
|
## Service availability
|
|
63
139
|
|
|
64
140
|
The store family needs `ctx.storageDomain`, which the web profile provides
|
package/lib/guard-tools.d.ts
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
* Demonstrates the §4.4 allow/deny/ask gate the righthand plugin uses to
|
|
4
4
|
* gate high-impact deploy/invoke tools. Built on ctx.on('tools/pre-execute'),
|
|
5
5
|
* not a hand-rolled wrapper.
|
|
6
|
+
*
|
|
7
|
+
* Rules are prefix-based. A rule may also declare `destructive: true` — a
|
|
8
|
+
* pure documentation flag saying the gated tools have irreversible effects
|
|
9
|
+
* (deploy, delete, DNS change): the kind of surface a tool *description*
|
|
10
|
+
* (attacker-controlled text) could talk the agent into. The flag does not
|
|
11
|
+
* change enforcement — `mode` does — it records WHY the prefix is guarded,
|
|
12
|
+
* and `tests/permissions.golden` records it so guard changes are never
|
|
13
|
+
* silent.
|
|
6
14
|
* @module dsh-righthand/guard-tools
|
|
7
15
|
*/
|
|
8
16
|
import type { Context } from '@deepseek-ai/cordis';
|
|
@@ -13,9 +21,30 @@ export interface GuardRule {
|
|
|
13
21
|
toolPrefix: string;
|
|
14
22
|
/** 'allow' passes through; 'deny' blocks; 'ask' defers to a policy function. */
|
|
15
23
|
mode: 'allow' | 'deny' | 'ask';
|
|
24
|
+
/**
|
|
25
|
+
* Documentation flag: the gated tools have irreversible effects nobody
|
|
26
|
+
* asked for (a deploy, a delete, a DNS change). Declarative only —
|
|
27
|
+
* enforcement is the rule's `mode`. Default false.
|
|
28
|
+
*/
|
|
29
|
+
destructive?: boolean;
|
|
16
30
|
/** Only consulted when mode is 'ask'. Return true to allow. */
|
|
17
31
|
ask?: (args: unknown) => boolean;
|
|
18
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* The guard facts a tool name resolves to: the mode the first matching rule
|
|
35
|
+
* enforces (or 'none' when no rule matches) and whether that rule marks the
|
|
36
|
+
* prefix destructive. This is the SINGLE lookup both the enforcement hook
|
|
37
|
+
* and the permissions golden use, so the recorded facts cannot drift from
|
|
38
|
+
* what the gate actually does.
|
|
39
|
+
*/
|
|
40
|
+
export interface GuardFacts {
|
|
41
|
+
mode: 'allow' | 'deny' | 'ask' | 'none';
|
|
42
|
+
destructive: boolean;
|
|
43
|
+
/** The matched rule, when one matched — the enforcement hook's ask path needs it. */
|
|
44
|
+
rule: GuardRule | undefined;
|
|
45
|
+
}
|
|
46
|
+
/** Resolve one tool name to its guard facts. Rules match in order, first wins. */
|
|
47
|
+
export declare function guardFactsFor(toolName: string, rules: readonly GuardRule[]): GuardFacts;
|
|
19
48
|
/** Apply a guard over a set of tool-name prefixes. */
|
|
20
49
|
export declare function apply(ctx: Context, config?: {
|
|
21
50
|
rules?: GuardRule[];
|
package/lib/guard-tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guard-tools.d.ts","sourceRoot":"","sources":["../src/guard-tools.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"guard-tools.d.ts","sourceRoot":"","sources":["../src/guard-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,eAAO,MAAM,IAAI,oBAAoB,CAAA;AACrC,eAAO,MAAM,MAAM,UAAY,CAAA;AAE/B,MAAM,WAAW,SAAS;IACxB,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAA;IAClB,gFAAgF;IAChF,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAA;IAC9B;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,+DAA+D;IAC/D,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAA;CACjC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAA;IACvC,WAAW,EAAE,OAAO,CAAA;IACpB,qFAAqF;IACrF,IAAI,EAAE,SAAS,GAAG,SAAS,CAAA;CAC5B;AAED,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,UAAU,CAIvF;AAED,sDAAsD;AACtD,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE;IAAE,KAAK,CAAC,EAAE,SAAS,EAAE,CAAA;CAAO,GAAG,IAAI,CAa9E"}
|
package/lib/guard-tools.js
CHANGED
|
@@ -3,26 +3,38 @@
|
|
|
3
3
|
* Demonstrates the §4.4 allow/deny/ask gate the righthand plugin uses to
|
|
4
4
|
* gate high-impact deploy/invoke tools. Built on ctx.on('tools/pre-execute'),
|
|
5
5
|
* not a hand-rolled wrapper.
|
|
6
|
+
*
|
|
7
|
+
* Rules are prefix-based. A rule may also declare `destructive: true` — a
|
|
8
|
+
* pure documentation flag saying the gated tools have irreversible effects
|
|
9
|
+
* (deploy, delete, DNS change): the kind of surface a tool *description*
|
|
10
|
+
* (attacker-controlled text) could talk the agent into. The flag does not
|
|
11
|
+
* change enforcement — `mode` does — it records WHY the prefix is guarded,
|
|
12
|
+
* and `tests/permissions.golden` records it so guard changes are never
|
|
13
|
+
* silent.
|
|
6
14
|
* @module dsh-righthand/guard-tools
|
|
7
15
|
*/
|
|
8
16
|
export const name = 'righthand-guard';
|
|
9
17
|
export const inject = ['tools'];
|
|
18
|
+
/** Resolve one tool name to its guard facts. Rules match in order, first wins. */
|
|
19
|
+
export function guardFactsFor(toolName, rules) {
|
|
20
|
+
const rule = rules.find(r => toolName.startsWith(r.toolPrefix));
|
|
21
|
+
if (rule === undefined)
|
|
22
|
+
return { mode: 'none', destructive: false, rule: undefined };
|
|
23
|
+
return { mode: rule.mode, destructive: rule.destructive === true, rule };
|
|
24
|
+
}
|
|
10
25
|
/** Apply a guard over a set of tool-name prefixes. */
|
|
11
26
|
export function apply(ctx, config = {}) {
|
|
12
27
|
const rules = config.rules ?? [];
|
|
13
28
|
ctx.on('tools/pre-execute', (exec, next) => {
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
29
|
+
const facts = guardFactsFor(exec.name, rules);
|
|
30
|
+
if (facts.mode === 'none' || facts.mode === 'allow')
|
|
16
31
|
return next();
|
|
17
|
-
if (
|
|
32
|
+
if (facts.mode === 'deny') {
|
|
18
33
|
throw new Error(`tool ${exec.name} is denied by righthand guard`);
|
|
19
34
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
throw new Error(`tool ${exec.name} requires approval (ask returned false)`);
|
|
24
|
-
return next();
|
|
25
|
-
}
|
|
35
|
+
const allowed = facts.rule?.ask?.(exec.arguments) ?? false;
|
|
36
|
+
if (!allowed)
|
|
37
|
+
throw new Error(`tool ${exec.name} requires approval (ask returned false)`);
|
|
26
38
|
return next();
|
|
27
39
|
}, { prepend: true });
|
|
28
40
|
}
|
package/lib/guard-tools.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guard-tools.js","sourceRoot":"","sources":["../src/guard-tools.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"guard-tools.js","sourceRoot":"","sources":["../src/guard-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,CAAC,MAAM,IAAI,GAAG,iBAAiB,CAAA;AACrC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,CAAA;AA+B/B,kFAAkF;AAClF,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,KAA2B;IACzE,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAA;IAC/D,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;IACpF,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,KAAK,IAAI,EAAE,IAAI,EAAE,CAAA;AAC1E,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,KAAK,CAAC,GAAY,EAAE,SAAkC,EAAE;IACtE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAA;IAEhC,GAAG,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;QACzC,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QAC7C,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,IAAI,EAAE,CAAA;QAClE,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,CAAC,IAAI,+BAA+B,CAAC,CAAA;QACnE,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,KAAK,CAAA;QAC1D,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,CAAC,IAAI,yCAAyC,CAAC,CAAA;QACzF,OAAO,IAAI,EAAE,CAAA;IACf,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;AACvB,CAAC"}
|
package/lib/index.d.ts
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* the harness's own services (storageDomain, credentials, settings,
|
|
6
6
|
* subprocess, jobs, tools), not hand-rolled primitives.
|
|
7
7
|
*
|
|
8
|
-
* The package's plugin entry (name/inject/apply) mounts the
|
|
9
|
-
*
|
|
8
|
+
* The package's plugin entry (name/inject/apply) mounts the tool families
|
|
9
|
+
* and the packaged skill as child fibers; each child waits on its own
|
|
10
|
+
* services, so a
|
|
10
11
|
* profile without `storageDomain` (e.g. headless) simply gets the other
|
|
11
12
|
* seven tools instead of failing the boot.
|
|
12
13
|
*
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
* - secrets-tools: rh_credential_describe/set/unset + rh_settings_get/set
|
|
16
17
|
* - exec-tools: rh_run / rh_run_bg over ctx.subprocess + ctx.jobs
|
|
17
18
|
* - guard-tools: tools/pre-execute policy (config.rules)
|
|
19
|
+
* - skills: the packaged dsh-righthand skill (ctx.skills)
|
|
18
20
|
*
|
|
19
21
|
* @module @try-works/dsh-righthand
|
|
20
22
|
*/
|
|
@@ -23,6 +25,7 @@ import * as storeTools from './store-tools.ts';
|
|
|
23
25
|
import * as secretsTools from './secrets-tools.ts';
|
|
24
26
|
import * as execTools from './exec-tools.ts';
|
|
25
27
|
import * as guardTools from './guard-tools.ts';
|
|
28
|
+
import * as righthandSkills from './skills.ts';
|
|
26
29
|
import type { GuardRule } from './guard-tools.ts';
|
|
27
30
|
export declare const name = "dsh-righthand";
|
|
28
31
|
/** The combined plugin needs no services itself; each child waits on its own. */
|
|
@@ -39,6 +42,9 @@ export interface RighthandConfig {
|
|
|
39
42
|
* instead of failing the whole plugin.
|
|
40
43
|
*/
|
|
41
44
|
export declare function apply(ctx: Context, config?: RighthandConfig): void;
|
|
42
|
-
export { storeTools, secretsTools, execTools, guardTools };
|
|
45
|
+
export { storeTools, secretsTools, execTools, guardTools, righthandSkills };
|
|
46
|
+
export { SKILL_NAME, SKILL_DESCRIPTION, skillDirectory, skillBody } from './skills.ts';
|
|
43
47
|
export type { GuardRule } from './guard-tools.ts';
|
|
48
|
+
export { guardFactsFor } from './guard-tools.ts';
|
|
49
|
+
export type { GuardFacts } from './guard-tools.ts';
|
|
44
50
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAA;AAClD,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAA;AAC5C,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,eAAe,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,eAAO,MAAM,IAAI,kBAAkB,CAAA;AAEnC,iFAAiF;AACjF,eAAO,MAAM,MAAM,EAAE,MAAM,EAAO,CAAA;AAElC,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,KAAK,CAAC,EAAE,SAAS,EAAE,CAAA;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,eAAoB,GAAG,IAAI,CAMtE;AAGD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,CAAA;AAC3E,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACtF,YAAY,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA"}
|
package/lib/index.js
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* the harness's own services (storageDomain, credentials, settings,
|
|
6
6
|
* subprocess, jobs, tools), not hand-rolled primitives.
|
|
7
7
|
*
|
|
8
|
-
* The package's plugin entry (name/inject/apply) mounts the
|
|
9
|
-
*
|
|
8
|
+
* The package's plugin entry (name/inject/apply) mounts the tool families
|
|
9
|
+
* and the packaged skill as child fibers; each child waits on its own
|
|
10
|
+
* services, so a
|
|
10
11
|
* profile without `storageDomain` (e.g. headless) simply gets the other
|
|
11
12
|
* seven tools instead of failing the boot.
|
|
12
13
|
*
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
* - secrets-tools: rh_credential_describe/set/unset + rh_settings_get/set
|
|
16
17
|
* - exec-tools: rh_run / rh_run_bg over ctx.subprocess + ctx.jobs
|
|
17
18
|
* - guard-tools: tools/pre-execute policy (config.rules)
|
|
19
|
+
* - skills: the packaged dsh-righthand skill (ctx.skills)
|
|
18
20
|
*
|
|
19
21
|
* @module @try-works/dsh-righthand
|
|
20
22
|
*/
|
|
@@ -22,6 +24,7 @@ import * as storeTools from "./store-tools.js";
|
|
|
22
24
|
import * as secretsTools from "./secrets-tools.js";
|
|
23
25
|
import * as execTools from "./exec-tools.js";
|
|
24
26
|
import * as guardTools from "./guard-tools.js";
|
|
27
|
+
import * as righthandSkills from "./skills.js";
|
|
25
28
|
export const name = 'dsh-righthand';
|
|
26
29
|
/** The combined plugin needs no services itself; each child waits on its own. */
|
|
27
30
|
export const inject = [];
|
|
@@ -36,7 +39,10 @@ export function apply(ctx, config = {}) {
|
|
|
36
39
|
ctx.plugin(secretsTools);
|
|
37
40
|
ctx.plugin(execTools);
|
|
38
41
|
ctx.plugin(guardTools, { rules: config.rules ?? [] });
|
|
42
|
+
ctx.plugin(righthandSkills);
|
|
39
43
|
}
|
|
40
44
|
// Individual modules stay importable for selective mounting.
|
|
41
|
-
export { storeTools, secretsTools, execTools, guardTools };
|
|
45
|
+
export { storeTools, secretsTools, execTools, guardTools, righthandSkills };
|
|
46
|
+
export { SKILL_NAME, SKILL_DESCRIPTION, skillDirectory, skillBody } from "./skills.js";
|
|
47
|
+
export { guardFactsFor } from "./guard-tools.js";
|
|
42
48
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAA;AAClD,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAA;AAC5C,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,eAAe,MAAM,aAAa,CAAA;AAG9C,MAAM,CAAC,MAAM,IAAI,GAAG,eAAe,CAAA;AAEnC,iFAAiF;AACjF,MAAM,CAAC,MAAM,MAAM,GAAa,EAAE,CAAA;AAQlC;;;;;GAKG;AACH,MAAM,UAAU,KAAK,CAAC,GAAY,EAAE,SAA0B,EAAE;IAC9D,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;IACtB,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IACxB,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IACrB,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,CAAA;IACrD,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,CAAA;AAC7B,CAAC;AAED,6DAA6D;AAC7D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,CAAA;AAC3E,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEtF,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA"}
|
package/lib/skills.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bundled dsh-righthand skill registration.
|
|
3
|
+
*
|
|
4
|
+
* Lives at `skills/dsh-righthand/SKILL.md` and registers with a directory
|
|
5
|
+
* `resourceBase` pointing at the bundled copy, so any relative references
|
|
6
|
+
* inside the SKILL.md resolve against the package's own files (the
|
|
7
|
+
* dsh-plugin packaged-skill standard, same shape as dsh-anti-slop).
|
|
8
|
+
*
|
|
9
|
+
* @module @try-works/dsh-righthand/src/skills
|
|
10
|
+
*/
|
|
11
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
12
|
+
export declare const name = "righthand-skills";
|
|
13
|
+
export declare const inject: string[];
|
|
14
|
+
export declare const SKILL_NAME = "dsh-righthand";
|
|
15
|
+
/** Routing description shown by skill discovery (matches the SKILL.md frontmatter). */
|
|
16
|
+
export declare const SKILL_DESCRIPTION = "Use when a task needs the righthand toolkit \u2014 durable key-value storage (rh_store_*), credential and settings management (rh_credential_* / rh_settings_*), governed command execution (rh_run / rh_run_bg), or its tool guard policy. Covers tool reference, guard semantics, and service availability.";
|
|
17
|
+
/** The bundled skill directory (this module lives in the bundle at lib/skills.js). */
|
|
18
|
+
export declare function skillDirectory(): string;
|
|
19
|
+
/** The skill body: SKILL.md verbatim from the bundled skill directory. */
|
|
20
|
+
export declare function skillBody(): string;
|
|
21
|
+
/** Register the packaged skill. Returns the cordis effect disposer. */
|
|
22
|
+
export declare function apply(ctx: Context): void;
|
|
23
|
+
//# sourceMappingURL=skills.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAMlD,eAAO,MAAM,IAAI,qBAAqB,CAAA;AACtC,eAAO,MAAM,MAAM,UAAa,CAAA;AAEhC,eAAO,MAAM,UAAU,kBAAkB,CAAA;AAEzC,uFAAuF;AACvF,eAAO,MAAM,iBAAiB,kTAA6S,CAAA;AAE3U,sFAAsF;AACtF,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,0EAA0E;AAC1E,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,uEAAuE;AACvE,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAQxC"}
|
package/lib/skills.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bundled dsh-righthand skill registration.
|
|
3
|
+
*
|
|
4
|
+
* Lives at `skills/dsh-righthand/SKILL.md` and registers with a directory
|
|
5
|
+
* `resourceBase` pointing at the bundled copy, so any relative references
|
|
6
|
+
* inside the SKILL.md resolve against the package's own files (the
|
|
7
|
+
* dsh-plugin packaged-skill standard, same shape as dsh-anti-slop).
|
|
8
|
+
*
|
|
9
|
+
* @module @try-works/dsh-righthand/src/skills
|
|
10
|
+
*/
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
import { dirname, join } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
export const name = 'righthand-skills';
|
|
15
|
+
export const inject = ['skills'];
|
|
16
|
+
export const SKILL_NAME = 'dsh-righthand';
|
|
17
|
+
/** Routing description shown by skill discovery (matches the SKILL.md frontmatter). */
|
|
18
|
+
export const SKILL_DESCRIPTION = 'Use when a task needs the righthand toolkit — durable key-value storage (rh_store_*), credential and settings management (rh_credential_* / rh_settings_*), governed command execution (rh_run / rh_run_bg), or its tool guard policy. Covers tool reference, guard semantics, and service availability.';
|
|
19
|
+
/** The bundled skill directory (this module lives in the bundle at lib/skills.js). */
|
|
20
|
+
export function skillDirectory() {
|
|
21
|
+
return join(dirname(fileURLToPath(import.meta.url)), '..', `skills/${SKILL_NAME}`);
|
|
22
|
+
}
|
|
23
|
+
/** The skill body: SKILL.md verbatim from the bundled skill directory. */
|
|
24
|
+
export function skillBody() {
|
|
25
|
+
return readFileSync(join(skillDirectory(), 'SKILL.md'), 'utf8');
|
|
26
|
+
}
|
|
27
|
+
/** Register the packaged skill. Returns the cordis effect disposer. */
|
|
28
|
+
export function apply(ctx) {
|
|
29
|
+
ctx.skills.register({
|
|
30
|
+
name: SKILL_NAME,
|
|
31
|
+
description: SKILL_DESCRIPTION,
|
|
32
|
+
source: 'bundled',
|
|
33
|
+
content: skillBody(),
|
|
34
|
+
resourceBase: { kind: 'directory', path: skillDirectory() },
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAItC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,MAAM,CAAC,MAAM,IAAI,GAAG,kBAAkB,CAAA;AACtC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAA;AAEhC,MAAM,CAAC,MAAM,UAAU,GAAG,eAAe,CAAA;AAEzC,uFAAuF;AACvF,MAAM,CAAC,MAAM,iBAAiB,GAAG,0SAA0S,CAAA;AAE3U,sFAAsF;AACtF,MAAM,UAAU,cAAc;IAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,UAAU,EAAE,CAAC,CAAA;AACpF,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,SAAS;IACvB,OAAO,YAAY,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,CAAA;AACjE,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,KAAK,CAAC,GAAY;IAChC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC;QAClB,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,iBAAiB;QAC9B,MAAM,EAAE,SAAS;QACjB,OAAO,EAAE,SAAS,EAAE;QACpB,YAAY,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE;KAC5D,CAAC,CAAA;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@try-works/dsh-righthand",
|
|
3
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.
|
|
4
|
+
"version": "0.1.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"@deepseek-ai/dsh-settings": "^0.1.0-rc.5",
|
|
29
29
|
"@deepseek-ai/dsh-subprocess": "^0.1.0-rc.5",
|
|
30
30
|
"@deepseek-ai/dsh-jobs": "^0.1.0-rc.5",
|
|
31
|
-
"@deepseek-ai/schemastery": "^3.18.1"
|
|
31
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
32
|
+
"@deepseek-ai/dsh-skill": "^0.1.0-rc.5"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
35
|
"@deepseek-ai/cordis": "file:D:/deepseek-harness/vendor/cordis",
|
|
@@ -72,6 +73,7 @@
|
|
|
72
73
|
"files": [
|
|
73
74
|
"lib",
|
|
74
75
|
"src",
|
|
76
|
+
"skills",
|
|
75
77
|
"cordis.patch.yml",
|
|
76
78
|
"README.md",
|
|
77
79
|
"LICENSE"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dsh-righthand
|
|
3
|
+
description: Use when a task needs the righthand toolkit — durable key-value storage (rh_store_*), credential and settings management (rh_credential_* / rh_settings_*), governed command execution (rh_run / rh_run_bg), or its tool guard policy. Covers tool reference, guard semantics, and service availability.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# dsh-righthand
|
|
7
|
+
|
|
8
|
+
The righthand toolkit: DSH-native tools over the harness's own services. Use this skill as the **tool reference** when any `rh_*` tool applies.
|
|
9
|
+
|
|
10
|
+
## Tool families
|
|
11
|
+
|
|
12
|
+
| Family | Tools | Backing service | Use when |
|
|
13
|
+
|--------|-------|-----------------|----------|
|
|
14
|
+
| store | `rh_store_put` / `rh_store_get` / `rh_store_delete` / `rh_store_list` | `ctx.storageDomain` (domain KV) | The task needs durable state across turns: a catalog, counter, cache, or note. Prefer this over files for structured JSON records. |
|
|
15
|
+
| secrets | `rh_credential_describe` / `rh_credential_set` / `rh_credential_unset` | `ctx.credentials` | The task must check, store, or remove a credential reference (e.g. `CLOUDFLARE_API_TOKEN`). Values are written durably and **never echoed back**. |
|
|
16
|
+
| settings | `rh_settings_get` / `rh_settings_set` | `ctx.settings` (namespace `righthand`) | Reading or patching righthand settings (`accountId`, `defaultZone`, `defaultScriptPrefix`). |
|
|
17
|
+
| exec | `rh_run` / `rh_run_bg` | `ctx.subprocess` + `ctx.jobs` | Running one command (argv array, no shell interpretation). `rh_run` collects bounded output; `rh_run_bg` starts an owner-scoped background job and returns its id. |
|
|
18
|
+
| guard | (policy, not a tool) | `ctx.tools` `tools/pre-execute` | Configured via plugin config `rules`; gates tools by name prefix with `allow` / `deny` / `ask` modes. |
|
|
19
|
+
|
|
20
|
+
## Store semantics
|
|
21
|
+
|
|
22
|
+
- One domain `righthand_store`: a `rows` table (string key → JSON value + timestamp) and a global write counter.
|
|
23
|
+
- `rh_store_get` returns `{ found, key, value?, updatedAt? }` — `found: false` means the key is absent, not an error.
|
|
24
|
+
- Writes are durable (backend flush before commit) and serialized on one write chain; values must be JSON-serializable.
|
|
25
|
+
|
|
26
|
+
## Credential semantics
|
|
27
|
+
|
|
28
|
+
- `rh_credential_describe` reports `configured`, `source`, `writable` — never the value.
|
|
29
|
+
- `rh_credential_set` requires a non-empty value and returns only `{ ref, stored: true }`.
|
|
30
|
+
- References are POSIX env-var style names (uppercase, e.g. `CLOUDFLARE_API_TOKEN`).
|
|
31
|
+
|
|
32
|
+
## Settings namespace `righthand`
|
|
33
|
+
|
|
34
|
+
| Key | Default | Meaning |
|
|
35
|
+
|-----|---------|---------|
|
|
36
|
+
| `accountId` | `""` | Cloudflare account id used by righthand Cloudflare tools |
|
|
37
|
+
| `defaultScriptPrefix` | `"rh-"` | default name prefix for generated workers/scripts |
|
|
38
|
+
| `defaultZone` | `""` | default Cloudflare zone |
|
|
39
|
+
|
|
40
|
+
## Guard policy
|
|
41
|
+
|
|
42
|
+
Rules come from the plugin config (`rules: [{ toolPrefix, mode, ask?, destructive? }]`). `deny` throws before dispatch; `ask` defers to the policy function (return true to allow); `allow` passes through. Tools matching no rule are unaffected. Prefixes match by prefix — `rh_run` also covers `rh_run_bg`.
|
|
43
|
+
|
|
44
|
+
`destructive: true` is a documentation flag for irreversible effects (deploy, delete, DNS change); it never changes enforcement, only records why a prefix is gated. `tests/permissions.golden` records every tool's derived guard facts; a guard change that moves a fact fails the test until re-baselined (`UPDATE_GOLDEN=1 pnpm test`).
|
|
45
|
+
|
|
46
|
+
## Service availability
|
|
47
|
+
|
|
48
|
+
- **web profile**: all twelve tools register (`storageDomain` is provided by the web app rows).
|
|
49
|
+
- **other profiles** (e.g. headless): the store family stays dormant; the other seven tools still register. The plugin never fails the boot.
|
package/src/guard-tools.ts
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
* Demonstrates the §4.4 allow/deny/ask gate the righthand plugin uses to
|
|
4
4
|
* gate high-impact deploy/invoke tools. Built on ctx.on('tools/pre-execute'),
|
|
5
5
|
* not a hand-rolled wrapper.
|
|
6
|
+
*
|
|
7
|
+
* Rules are prefix-based. A rule may also declare `destructive: true` — a
|
|
8
|
+
* pure documentation flag saying the gated tools have irreversible effects
|
|
9
|
+
* (deploy, delete, DNS change): the kind of surface a tool *description*
|
|
10
|
+
* (attacker-controlled text) could talk the agent into. The flag does not
|
|
11
|
+
* change enforcement — `mode` does — it records WHY the prefix is guarded,
|
|
12
|
+
* and `tests/permissions.golden` records it so guard changes are never
|
|
13
|
+
* silent.
|
|
6
14
|
* @module dsh-righthand/guard-tools
|
|
7
15
|
*/
|
|
8
16
|
|
|
@@ -16,25 +24,49 @@ export interface GuardRule {
|
|
|
16
24
|
toolPrefix: string
|
|
17
25
|
/** 'allow' passes through; 'deny' blocks; 'ask' defers to a policy function. */
|
|
18
26
|
mode: 'allow' | 'deny' | 'ask'
|
|
27
|
+
/**
|
|
28
|
+
* Documentation flag: the gated tools have irreversible effects nobody
|
|
29
|
+
* asked for (a deploy, a delete, a DNS change). Declarative only —
|
|
30
|
+
* enforcement is the rule's `mode`. Default false.
|
|
31
|
+
*/
|
|
32
|
+
destructive?: boolean
|
|
19
33
|
/** Only consulted when mode is 'ask'. Return true to allow. */
|
|
20
34
|
ask?: (args: unknown) => boolean
|
|
21
35
|
}
|
|
22
36
|
|
|
37
|
+
/**
|
|
38
|
+
* The guard facts a tool name resolves to: the mode the first matching rule
|
|
39
|
+
* enforces (or 'none' when no rule matches) and whether that rule marks the
|
|
40
|
+
* prefix destructive. This is the SINGLE lookup both the enforcement hook
|
|
41
|
+
* and the permissions golden use, so the recorded facts cannot drift from
|
|
42
|
+
* what the gate actually does.
|
|
43
|
+
*/
|
|
44
|
+
export interface GuardFacts {
|
|
45
|
+
mode: 'allow' | 'deny' | 'ask' | 'none'
|
|
46
|
+
destructive: boolean
|
|
47
|
+
/** The matched rule, when one matched — the enforcement hook's ask path needs it. */
|
|
48
|
+
rule: GuardRule | undefined
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Resolve one tool name to its guard facts. Rules match in order, first wins. */
|
|
52
|
+
export function guardFactsFor(toolName: string, rules: readonly GuardRule[]): GuardFacts {
|
|
53
|
+
const rule = rules.find(r => toolName.startsWith(r.toolPrefix))
|
|
54
|
+
if (rule === undefined) return { mode: 'none', destructive: false, rule: undefined }
|
|
55
|
+
return { mode: rule.mode, destructive: rule.destructive === true, rule }
|
|
56
|
+
}
|
|
57
|
+
|
|
23
58
|
/** Apply a guard over a set of tool-name prefixes. */
|
|
24
59
|
export function apply(ctx: Context, config: { rules?: GuardRule[] } = {}): void {
|
|
25
60
|
const rules = config.rules ?? []
|
|
26
61
|
|
|
27
62
|
ctx.on('tools/pre-execute', (exec, next) => {
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
if (
|
|
63
|
+
const facts = guardFactsFor(exec.name, rules)
|
|
64
|
+
if (facts.mode === 'none' || facts.mode === 'allow') return next()
|
|
65
|
+
if (facts.mode === 'deny') {
|
|
31
66
|
throw new Error(`tool ${exec.name} is denied by righthand guard`)
|
|
32
67
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (!allowed) throw new Error(`tool ${exec.name} requires approval (ask returned false)`)
|
|
36
|
-
return next()
|
|
37
|
-
}
|
|
68
|
+
const allowed = facts.rule?.ask?.(exec.arguments) ?? false
|
|
69
|
+
if (!allowed) throw new Error(`tool ${exec.name} requires approval (ask returned false)`)
|
|
38
70
|
return next()
|
|
39
71
|
}, { prepend: true })
|
|
40
|
-
}
|
|
72
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* the harness's own services (storageDomain, credentials, settings,
|
|
6
6
|
* subprocess, jobs, tools), not hand-rolled primitives.
|
|
7
7
|
*
|
|
8
|
-
* The package's plugin entry (name/inject/apply) mounts the
|
|
9
|
-
*
|
|
8
|
+
* The package's plugin entry (name/inject/apply) mounts the tool families
|
|
9
|
+
* and the packaged skill as child fibers; each child waits on its own
|
|
10
|
+
* services, so a
|
|
10
11
|
* profile without `storageDomain` (e.g. headless) simply gets the other
|
|
11
12
|
* seven tools instead of failing the boot.
|
|
12
13
|
*
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
* - secrets-tools: rh_credential_describe/set/unset + rh_settings_get/set
|
|
16
17
|
* - exec-tools: rh_run / rh_run_bg over ctx.subprocess + ctx.jobs
|
|
17
18
|
* - guard-tools: tools/pre-execute policy (config.rules)
|
|
19
|
+
* - skills: the packaged dsh-righthand skill (ctx.skills)
|
|
18
20
|
*
|
|
19
21
|
* @module @try-works/dsh-righthand
|
|
20
22
|
*/
|
|
@@ -24,6 +26,7 @@ import * as storeTools from './store-tools.ts'
|
|
|
24
26
|
import * as secretsTools from './secrets-tools.ts'
|
|
25
27
|
import * as execTools from './exec-tools.ts'
|
|
26
28
|
import * as guardTools from './guard-tools.ts'
|
|
29
|
+
import * as righthandSkills from './skills.ts'
|
|
27
30
|
import type { GuardRule } from './guard-tools.ts'
|
|
28
31
|
|
|
29
32
|
export const name = 'dsh-righthand'
|
|
@@ -48,9 +51,13 @@ export function apply(ctx: Context, config: RighthandConfig = {}): void {
|
|
|
48
51
|
ctx.plugin(secretsTools)
|
|
49
52
|
ctx.plugin(execTools)
|
|
50
53
|
ctx.plugin(guardTools, { rules: config.rules ?? [] })
|
|
54
|
+
ctx.plugin(righthandSkills)
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
// Individual modules stay importable for selective mounting.
|
|
54
|
-
export { storeTools, secretsTools, execTools, guardTools }
|
|
58
|
+
export { storeTools, secretsTools, execTools, guardTools, righthandSkills }
|
|
59
|
+
export { SKILL_NAME, SKILL_DESCRIPTION, skillDirectory, skillBody } from './skills.ts'
|
|
55
60
|
export type { GuardRule } from './guard-tools.ts'
|
|
61
|
+
export { guardFactsFor } from './guard-tools.ts'
|
|
62
|
+
export type { GuardFacts } from './guard-tools.ts'
|
|
56
63
|
|
package/src/skills.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bundled dsh-righthand skill registration.
|
|
3
|
+
*
|
|
4
|
+
* Lives at `skills/dsh-righthand/SKILL.md` and registers with a directory
|
|
5
|
+
* `resourceBase` pointing at the bundled copy, so any relative references
|
|
6
|
+
* inside the SKILL.md resolve against the package's own files (the
|
|
7
|
+
* dsh-plugin packaged-skill standard, same shape as dsh-anti-slop).
|
|
8
|
+
*
|
|
9
|
+
* @module @try-works/dsh-righthand/src/skills
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFileSync } from 'node:fs'
|
|
13
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
14
|
+
// Type-only import activates the cordis Context augmentation for `ctx.skills`.
|
|
15
|
+
import type {} from '@deepseek-ai/dsh-skill'
|
|
16
|
+
import { dirname, join } from 'node:path'
|
|
17
|
+
import { fileURLToPath } from 'node:url'
|
|
18
|
+
|
|
19
|
+
export const name = 'righthand-skills'
|
|
20
|
+
export const inject = ['skills']
|
|
21
|
+
|
|
22
|
+
export const SKILL_NAME = 'dsh-righthand'
|
|
23
|
+
|
|
24
|
+
/** Routing description shown by skill discovery (matches the SKILL.md frontmatter). */
|
|
25
|
+
export const SKILL_DESCRIPTION = 'Use when a task needs the righthand toolkit — durable key-value storage (rh_store_*), credential and settings management (rh_credential_* / rh_settings_*), governed command execution (rh_run / rh_run_bg), or its tool guard policy. Covers tool reference, guard semantics, and service availability.'
|
|
26
|
+
|
|
27
|
+
/** The bundled skill directory (this module lives in the bundle at lib/skills.js). */
|
|
28
|
+
export function skillDirectory(): string {
|
|
29
|
+
return join(dirname(fileURLToPath(import.meta.url)), '..', `skills/${SKILL_NAME}`)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The skill body: SKILL.md verbatim from the bundled skill directory. */
|
|
33
|
+
export function skillBody(): string {
|
|
34
|
+
return readFileSync(join(skillDirectory(), 'SKILL.md'), 'utf8')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Register the packaged skill. Returns the cordis effect disposer. */
|
|
38
|
+
export function apply(ctx: Context): void {
|
|
39
|
+
ctx.skills.register({
|
|
40
|
+
name: SKILL_NAME,
|
|
41
|
+
description: SKILL_DESCRIPTION,
|
|
42
|
+
source: 'bundled',
|
|
43
|
+
content: skillBody(),
|
|
44
|
+
resourceBase: { kind: 'directory', path: skillDirectory() },
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|