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 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).
@@ -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