dsh-keyword-injector 0.2.0-beta.1
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/LICENSE +21 -0
- package/README.md +163 -0
- package/cordis.patch.yml +12 -0
- package/lib/client.bundled.js +885 -0
- package/lib/client.js +206 -0
- package/lib/index.js +830 -0
- package/lib/matcher.js +147 -0
- package/lib/reminder.js +80 -0
- package/lib/schema.js +176 -0
- package/lib/ui.js +643 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mark Elayan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# dsh-keyword-injector
|
|
2
|
+
|
|
3
|
+
Keyword-triggered **context injection** for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH).
|
|
4
|
+
|
|
5
|
+
Define keywords once; the plugin watches a session and, when a trigger fires, injects your payload into the conversation as a **Context injection** message — the same delivery surface memos-local-memory uses. The reminder renders in the Web GUI as a collapsed `Context injection · dsh-keyword-injector` row and reaches the model with its next request. The agent's flow is never interrupted: no turns are started, nothing is steered, no extra model calls are made for the delivery itself.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
user: "please deploy to staging now"
|
|
9
|
+
└─ Context injection · dsh-keyword-injector ← collapsed row, model sees it too
|
|
10
|
+
[KEYWORD-INJECTOR REMINDER] …rules you configured… [END KEYWORD-INJECTOR REMINDER]
|
|
11
|
+
agent: (answers with your rules applied)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Why two trigger sources
|
|
15
|
+
|
|
16
|
+
Most injectors only watch user input. Real prompts often don't contain the word that matters — **the agent's own reasoning does**:
|
|
17
|
+
|
|
18
|
+
> User: *"resolve the issue"* → agent thinks *"I need to triage this first"* → **`triage` rules arrive on their own.*
|
|
19
|
+
|
|
20
|
+
| Source | Watches | Fires when |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| **`user`** (default) | What the user types | The triggering message lands in the session inbox — the reminder joins that message's step (reminder first, one model request) |
|
|
23
|
+
| **`agent`** | The agent's **own reasoning and actions** (assistant messages incl. thinking, tool calls + arguments, tool results) | The agent's step boundary — e.g. it *thinks* "I need to triage this" mid-task and the rules are injected for its **next step of the same turn** |
|
|
24
|
+
|
|
25
|
+
The matcher enforces source isolation (user triggers never fire on agent text and vice versa), and agent-source triggers fire **once per turn per trigger** — the agent echoing the keyword after reading the rules never re-triggers.
|
|
26
|
+
|
|
27
|
+
## Real use cases
|
|
28
|
+
|
|
29
|
+
1. **Standing rules on demand** — trigger `Follow the rules` (user source,
|
|
30
|
+
static-text) injects your personal operating law: keep the session to-do
|
|
31
|
+
list updated, use SEQUENTIAL THINKING for long problems, escalate to the
|
|
32
|
+
advisor when stuck, never work around broken system tools, clean up
|
|
33
|
+
leftover scripts. Three words typed; the full rulebook arrives as context.
|
|
34
|
+
2. **Skill steering** — trigger `context manager` (user source, steer-skill)
|
|
35
|
+
injects "load the `context-manager` skill" — the agent picks up the whole
|
|
36
|
+
methodology without pasted instructions.
|
|
37
|
+
3. **Triage rules the agent asks for itself** — the agent-source example
|
|
38
|
+
above: user says "resolve the issue", the agent's *own reasoning* matches
|
|
39
|
+
`triage`, rules arrive at its next step.
|
|
40
|
+
4. **Deploy guardrail** — trigger `deploy to staging` rides a smoke-test
|
|
41
|
+
reminder along with every matching request, in every session.
|
|
42
|
+
|
|
43
|
+
## Trigger model
|
|
44
|
+
|
|
45
|
+
| Field | Values | Notes |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| keyword | any text (≤ 200 chars) | what to match |
|
|
48
|
+
| matchType | `exact` / `contains` / `regex` | regex validated at save time; ReDoS-shaped patterns (e.g. `(a+)+`) rejected up front; runtime matcher has a sample cap + budget as a second guard; case-sensitivity toggle |
|
|
49
|
+
| mode | `static-text` / `steer-skill` | inject a verbatim payload, or an instruction to load a named skill (validated against the live skills catalog at save time) |
|
|
50
|
+
| source | `user` / `agent` | see above |
|
|
51
|
+
| scope | `all-sessions` / `this-session` | `this-session` only settable from inside a session |
|
|
52
|
+
| enabled | on/off | toggle straight from the table |
|
|
53
|
+
|
|
54
|
+
Payloads are wrapped in fixed `[KEYWORD-INJECTOR REMINDER] … [END KEYWORD-INJECTOR REMINDER]` delimiters and sanitised (invisible control bytes stripped), so user-supplied payload text can never pose as system instructions — the model always sees it as data inside a marked block.
|
|
55
|
+
|
|
56
|
+
## UI
|
|
57
|
+
|
|
58
|
+
Two surfaces, same manager:
|
|
59
|
+
|
|
60
|
+
1. **Sidebar → Keywords** — a main panel alongside Task Board / SSH / Skill Center.
|
|
61
|
+
2. **Settings → Keyword Injector**.
|
|
62
|
+
|
|
63
|
+
Both provide: trigger table (enabled toggle, keyword, match type, mode, source, payload preview, scope, edit/delete), filter box, and an edit modal with a **live test** panel — a host-side dry run using the *exact* runtime matcher, so the panel is always honest about what will fire.
|
|
64
|
+
|
|
65
|
+
## Agent tools
|
|
66
|
+
|
|
67
|
+
The model can manage triggers itself (useful for self-configuring agents):
|
|
68
|
+
|
|
69
|
+
- `keyword_injector_list_triggers`
|
|
70
|
+
- `keyword_injector_create_trigger` (skill targets validated)
|
|
71
|
+
- `keyword_injector_update_trigger`
|
|
72
|
+
- `keyword_injector_delete_trigger`
|
|
73
|
+
- `keyword_injector_test_trigger`
|
|
74
|
+
|
|
75
|
+
## Install
|
|
76
|
+
|
|
77
|
+
The plugin is a normal DSH bundle. From the web profile:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
dsh plugin --profile web add github:markelayan/dsh-keyword-injector
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
or with pnpm directly:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
cd ~/.dsh/profiles/web
|
|
87
|
+
pnpm add github:markelayan/dsh-keyword-injector
|
|
88
|
+
# then add "dsh-keyword-injector" to dsh.profile.bundles in package.json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Local-development (`link:`) installs: clone anywhere, run `npm install` (pulls
|
|
92
|
+
`schemastery`), then `dsh plugin --profile web add link:<path>`. Note for
|
|
93
|
+
`link:` users: DSH resolves a plugin's imports from its **realpath**, so the
|
|
94
|
+
cloned copy needs its own `node_modules` (the `npm install` above provides it).
|
|
95
|
+
|
|
96
|
+
Restart `dsh web` and hard-refresh the browser after installing.
|
|
97
|
+
|
|
98
|
+
Requires a DSH core from the 0.1.1-rc line (tested on 0.1.1-rc.2).
|
|
99
|
+
|
|
100
|
+
## Architecture
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
lib/
|
|
104
|
+
├── index.js host half — events, persistence, HTTP routes, agent tools
|
|
105
|
+
├── client.js client half SOURCE (sidebar entry, panel, settings page)
|
|
106
|
+
├── client.bundled.js SHIPPED client — esbuild bundle in ModuleLoader format
|
|
107
|
+
├── schema.js trigger validation (all trust boundaries)
|
|
108
|
+
├── matcher.js runtime matching (regex cache, budgets)
|
|
109
|
+
└── reminder.js reminder text formatting + sanitisation
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
- **Persistence**: settings namespace `dsh-keyword-injector` (ends up in
|
|
113
|
+
`~/.dsh/settings.yaml`), registered on boot with a permissive schemastery
|
|
114
|
+
schema; every read passes strict `validateDocument`, so a corrupted store
|
|
115
|
+
degrades to an empty trigger list — never a crash.
|
|
116
|
+
- **Client ↔ host**: bundle clients have no `host.call` bridge, so the UI
|
|
117
|
+
talks to plain HTTP routes on the host:
|
|
118
|
+
`POST /api/dsh-keyword-injector/{list,create,update,delete,test,skills}`.
|
|
119
|
+
- **Delivery**: reminders are durable, plugin-sourced `user/message` events
|
|
120
|
+
inserted through the agent inbox (`agent.inject()` → next-step queue), so
|
|
121
|
+
they are claimed together with the triggering message — reminder first —
|
|
122
|
+
and render as Context injection rows. The inbox claims exactly one
|
|
123
|
+
next-turn message per turn, so the plugin never prepends to that queue
|
|
124
|
+
(that would strand user messages).
|
|
125
|
+
- **Events used**: `agent/inbox/inserted` (user source) and
|
|
126
|
+
`agent/pre-step` (agent-source scanner).
|
|
127
|
+
|
|
128
|
+
### Rebuilding the client
|
|
129
|
+
|
|
130
|
+
The browser loader requires a single self-contained
|
|
131
|
+
`window.__ModuleLoader__.load` file, so the client ships **bundled**. After
|
|
132
|
+
editing `lib/client.js`:
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
npm install # once
|
|
136
|
+
npx esbuild lib/client.js --bundle --format=cjs \
|
|
137
|
+
--external:react --external:react-dom --external:react-dom/client \
|
|
138
|
+
--outfile=/tmp/kwi.cjs
|
|
139
|
+
# re-wrap with the ModuleLoader factory — see the exact steps in the header
|
|
140
|
+
# comment of lib/client.bundled.js
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Then restart `dsh web` and hard-refresh.
|
|
144
|
+
|
|
145
|
+
## Security & privacy
|
|
146
|
+
|
|
147
|
+
- **No network calls.** The client fetches same-origin DSH routes only; the
|
|
148
|
+
host touches local files (the DSH settings store) and in-process APIs.
|
|
149
|
+
No telemetry, no analytics, no outbound requests, ever.
|
|
150
|
+
- **No secrets.** The plugin stores no credentials and reads none.
|
|
151
|
+
- **Injection-hardened.** Reminder payloads are fenced in constant
|
|
152
|
+
delimiters, sanitised, and length-capped; the keyword itself is sanitised
|
|
153
|
+
before embedding. The model is told, in-band, that the block is configured
|
|
154
|
+
by the user — not instructions from the payload author.
|
|
155
|
+
- **ReDoS guards.** Regex triggers are shape-checked at save time and
|
|
156
|
+
sample-capped + budgeted at match time.
|
|
157
|
+
- **Self-matching impossible.** The scanner reads only genuine user input or
|
|
158
|
+
assistant/tool events — never the plugin's own injections — and fires at
|
|
159
|
+
most once per message (user source) or per turn (agent source).
|
|
160
|
+
|
|
161
|
+
## Changelog
|
|
162
|
+
|
|
163
|
+
See [CHANGELOG.md](./CHANGELOG.md). MIT license — see [LICENSE](./LICENSE).
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# cordis.patch.yml — bundle composition patch for dsh-keyword-injector.
|
|
2
|
+
#
|
|
3
|
+
# Self-mounts the plugin row into the web profile roster. Applied as a profile
|
|
4
|
+
# bundle layer (the `dsh.bundle.patch` manifest field in package.json) — the
|
|
5
|
+
# same mechanism dsh-drawio-links uses. MUST stay a top-level YAML array with
|
|
6
|
+
# an ACTIVE `- insert:` entry: an all-commented file parses to null and crashes
|
|
7
|
+
# boot ("must be a top-level YAML array of loader patch entries").
|
|
8
|
+
# NOTE: the profile-level cordis.patch.yml must NOT also insert this row —
|
|
9
|
+
# that would double-mount the plugin.
|
|
10
|
+
- insert:
|
|
11
|
+
- id: dsh-keyword-injector
|
|
12
|
+
name: dsh-keyword-injector
|