dsh-caveman 0.1.2 → 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/lib/client.js +21 -9
- package/lib/index.js +146 -68
- package/package.json +2 -5
package/lib/client.js
CHANGED
|
@@ -7,10 +7,10 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
const React = require("react");
|
|
8
8
|
|
|
9
9
|
// Badge display is centralized in dsh-badges: this plugin only registers
|
|
10
|
-
// a badge descriptor (
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
10
|
+
// a badge descriptor (fetch-mode: initial GET + toggle-response self-
|
|
11
|
+
// update + 5s re-sync). No session projection is involved; per-session
|
|
12
|
+
// mode lives in the host sidecar. Functionality (/caveman command,
|
|
13
|
+
// directive parsing, prompt-section injection) lives in the host half.
|
|
14
14
|
const inject = ["slots"];
|
|
15
15
|
|
|
16
16
|
function apply(ctx) {
|
|
@@ -24,18 +24,30 @@ window.__ModuleLoader__.load({
|
|
|
24
24
|
if (registry === undefined) return false;
|
|
25
25
|
registry.register({
|
|
26
26
|
key: "caveman",
|
|
27
|
-
|
|
27
|
+
fetch: (sessionId) =>
|
|
28
|
+
sessionId
|
|
29
|
+
? fetch("/caveman/state?sessionId=" + encodeURIComponent(sessionId))
|
|
30
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
31
|
+
.catch(() => null)
|
|
32
|
+
: Promise.resolve(null),
|
|
33
|
+
pollMs: 5000,
|
|
28
34
|
title: "Click to cycle caveman mode: off -> lite -> full -> ultra",
|
|
29
35
|
onClick: (sessionId) => {
|
|
30
|
-
|
|
36
|
+
// Cycles through the host sidecar (POST /caveman/toggle). The
|
|
37
|
+
// resolved value updates the badge immediately (no waiting for
|
|
38
|
+
// the next poll). Failures are silent (badge stays as-is).
|
|
39
|
+
if (!sessionId) return Promise.resolve(null);
|
|
31
40
|
try {
|
|
32
|
-
fetch("/caveman/toggle", {
|
|
41
|
+
return fetch("/caveman/toggle", {
|
|
33
42
|
method: "POST",
|
|
34
43
|
headers: { "content-type": "application/json" },
|
|
35
44
|
body: JSON.stringify({ sessionId }),
|
|
36
|
-
})
|
|
45
|
+
})
|
|
46
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
47
|
+
.then((data) => (data && data.ok ? { mode: data.mode } : null))
|
|
48
|
+
.catch(() => null);
|
|
37
49
|
} catch (e) {
|
|
38
|
-
|
|
50
|
+
return Promise.resolve(null);
|
|
39
51
|
}
|
|
40
52
|
},
|
|
41
53
|
render: (value) => {
|
package/lib/index.js
CHANGED
|
@@ -1,24 +1,41 @@
|
|
|
1
1
|
// dsh-caveman: per-session caveman communication mode.
|
|
2
|
-
// -
|
|
3
|
-
// (default 'full';
|
|
4
|
-
// - /caveman command
|
|
5
|
-
//
|
|
2
|
+
// - per-session mode state lives in a sidecar JSON keyed by session id
|
|
3
|
+
// (unseen sessions use the settings default 'full'; no cross-agent sharing)
|
|
4
|
+
// - /caveman command, plain-text "caveman <mode>" directive (agent/pre-step),
|
|
5
|
+
// and POST /caveman/toggle all flip the sidecar state; directive-only user
|
|
6
|
+
// messages are still consumed
|
|
6
7
|
// - system-prompt section injects the current mode as an overriding directive
|
|
7
|
-
// - composer dock badge (client half, lib/client.js) renders
|
|
8
|
-
//
|
|
8
|
+
// - composer dock badge (client half, lib/client.js) renders in fetch mode
|
|
9
|
+
// (GET /caveman/state + toggle response self-update + 5s re-sync)
|
|
9
10
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
import {
|
|
11
|
+
// NO-SESSION-EVENT DESIGN (a' refactor 2026-09-08):
|
|
12
|
+
// this plugin WRITES zero session events. The KNOWN registration (dual-path,
|
|
13
|
+
// below) is KEPT as read-side legacy compat only: rc.2 persistence refuses to
|
|
14
|
+
// load logs containing unregistered, non-ignorable event types
|
|
15
|
+
// (dsh-session-persistence L1120), and historic sessions contain
|
|
16
|
+
// `caveman/change` (实撞 2026-09-08 via rtk: dropping the registration breaks
|
|
17
|
+
// legacy log loading). Logs written before the old fix were repaired in place
|
|
18
|
+
// by .dsh/scripts/repair-caveman-events.py / fix-d8b43aa8.py.
|
|
19
|
+
import { createRequire } from 'node:module'
|
|
20
|
+
import { appendFileSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
|
|
21
|
+
import { dirname, join } from 'node:path'
|
|
21
22
|
import { KNOWN_SESSION_EVENT_TYPES } from '@deepseek-ai/dsh-session'
|
|
23
|
+
import { dshHomePath } from '@deepseek-ai/dsh-home-paths'
|
|
24
|
+
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
|
|
25
|
+
import zs from '@deepseek-ai/schemastery'
|
|
26
|
+
|
|
27
|
+
const require = createRequire(import.meta.url)
|
|
28
|
+
|
|
29
|
+
function registerKnownEventType(type) {
|
|
30
|
+
KNOWN_SESSION_EVENT_TYPES.add(type)
|
|
31
|
+
try {
|
|
32
|
+
const persistencePath = require.resolve('@deepseek-ai/dsh-session-persistence')
|
|
33
|
+
const hostSessionPath = require.resolve('@deepseek-ai/dsh-session', { paths: [persistencePath] })
|
|
34
|
+
require(hostSessionPath).KNOWN_SESSION_EVENT_TYPES.add(type)
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.warn('[caveman] could not register host session event type:', err && err.message)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
22
39
|
|
|
23
40
|
export const name = 'caveman'
|
|
24
41
|
// Hard dependency on the HTTP route registry for the badge-click toggle
|
|
@@ -29,8 +46,6 @@ const VALID = ['off', 'lite', 'full', 'ultra', 'wenyan-lite', 'wenyan-full', 'we
|
|
|
29
46
|
const DIRECTIVE_RE = /^\/?caveman\s+(off|lite|full|ultra|wenyan-lite|wenyan-full|wenyan-ultra)$/i
|
|
30
47
|
const OFF_RE = /^(stop\s+caveman|normal\s+mode)$/i
|
|
31
48
|
|
|
32
|
-
const schema = z.object({ mode: z.enum(VALID) })
|
|
33
|
-
|
|
34
49
|
function parseDirective(text) {
|
|
35
50
|
if (typeof text !== 'string') return null
|
|
36
51
|
const t = text.trim()
|
|
@@ -50,36 +65,81 @@ function directiveOf(message) {
|
|
|
50
65
|
}
|
|
51
66
|
|
|
52
67
|
export function apply(ctx) {
|
|
53
|
-
//
|
|
54
|
-
// containing `caveman/change`
|
|
68
|
+
// READ-SIDE LEGACY COMPAT ONLY (do not remove): keeps session logs
|
|
69
|
+
// containing historic `caveman/change` loadable; this plugin no longer
|
|
70
|
+
// WRITES session events (see header). Drop only after SOP §8 injection.
|
|
55
71
|
try {
|
|
56
|
-
|
|
72
|
+
registerKnownEventType('caveman/change')
|
|
57
73
|
} catch (err) {
|
|
58
74
|
console.warn('[caveman] could not register event type:', err && err.message)
|
|
59
75
|
}
|
|
60
76
|
|
|
61
|
-
|
|
77
|
+
// ---- global default mode (settings document, 'caveman' namespace) ------
|
|
78
|
+
// Sessions WITHOUT a sidecar entry follow this live default (full).
|
|
79
|
+
let defaultMode = 'full'
|
|
80
|
+
let cavemanScope = null
|
|
81
|
+
const settings = ctx.get('settings')
|
|
82
|
+
if (settings !== undefined) {
|
|
83
|
+
try {
|
|
84
|
+
cavemanScope = settings.register(
|
|
85
|
+
settingsNamespace('caveman'),
|
|
86
|
+
zs.object({ defaultMode: zs.union(VALID.map((v) => zs.const(v))).default('full') }),
|
|
87
|
+
{ applies: 'live' },
|
|
88
|
+
)
|
|
89
|
+
const value = cavemanScope.get()
|
|
90
|
+
defaultMode = value && VALID.includes(value.defaultMode) ? value.defaultMode : 'full'
|
|
91
|
+
cavemanScope.watch((next) => {
|
|
92
|
+
defaultMode = next && VALID.includes(next.defaultMode) ? next.defaultMode : 'full'
|
|
93
|
+
})
|
|
94
|
+
} catch (err) {
|
|
95
|
+
console.warn('[caveman] settings namespace registration failed:', err && err.message)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
62
98
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
99
|
+
// ---- per-session mode state: sidecar JSON keyed by session id ----------
|
|
100
|
+
// Truth source for the mode. Sessions WITHOUT an entry use the live
|
|
101
|
+
// settings default. Atomic tmp+rename persist; lives outside node_modules
|
|
102
|
+
// (survives restarts and plugin reinstalls).
|
|
103
|
+
const SIDECAR_PATH = (() => {
|
|
104
|
+
try {
|
|
105
|
+
return join(dshHomePath('plugin-state'), 'caveman-state.json')
|
|
106
|
+
} catch (e) {
|
|
107
|
+
console.warn('[caveman] dshHomePath unavailable, sidecar persistence disabled:', e && e.message)
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
})()
|
|
111
|
+
const sessionMode = new Map()
|
|
112
|
+
const loadSidecar = () => {
|
|
113
|
+
if (SIDECAR_PATH === null) return
|
|
114
|
+
try {
|
|
115
|
+
const data = JSON.parse(readFileSync(SIDECAR_PATH, 'utf8'))
|
|
116
|
+
const sessions = data && data.sessions
|
|
117
|
+
if (sessions && typeof sessions === 'object') {
|
|
118
|
+
for (const [id, mode] of Object.entries(sessions)) {
|
|
119
|
+
if (typeof mode === 'string' && VALID.includes(mode)) sessionMode.set(id, mode)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch (e) {
|
|
123
|
+
// missing or corrupt sidecar: start from an empty map (defaults apply)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const persistSidecar = () => {
|
|
127
|
+
if (SIDECAR_PATH === null) return
|
|
128
|
+
try {
|
|
129
|
+
mkdirSync(dirname(SIDECAR_PATH), { recursive: true })
|
|
130
|
+
const tmp = SIDECAR_PATH + '.tmp'
|
|
131
|
+
writeFileSync(tmp, JSON.stringify({ version: 1, sessions: Object.fromEntries(sessionMode) }), 'utf8')
|
|
132
|
+
renameSync(tmp, SIDECAR_PATH)
|
|
133
|
+
} catch (e) {
|
|
134
|
+
console.warn('[caveman] sidecar persist failed:', e && e.message)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const readMode = (sessionId) => (sessionMode.has(sessionId) ? sessionMode.get(sessionId) : defaultMode)
|
|
138
|
+
const writeMode = (sessionId, mode) => {
|
|
139
|
+
sessionMode.set(sessionId, mode)
|
|
140
|
+
persistSidecar()
|
|
82
141
|
}
|
|
142
|
+
loadSidecar()
|
|
83
143
|
|
|
84
144
|
const commands = ctx.get('commands')
|
|
85
145
|
if (commands !== undefined) {
|
|
@@ -92,7 +152,8 @@ export function apply(ctx) {
|
|
|
92
152
|
if (!VALID.includes(mode)) {
|
|
93
153
|
return { kind: 'error', text: 'Invalid mode: "' + invocation.rawInput.trim() + '". Valid: ' + VALID.join('|') }
|
|
94
154
|
}
|
|
95
|
-
invocation.agent.session
|
|
155
|
+
const sid = invocation.agent && invocation.agent.session && invocation.agent.session.id
|
|
156
|
+
if (typeof sid === 'string' && sid.length > 0) writeMode(sid, mode)
|
|
96
157
|
return { kind: 'success', text: mode === 'off' ? 'Caveman mode: off' : 'Caveman mode: ' + mode }
|
|
97
158
|
},
|
|
98
159
|
})
|
|
@@ -108,17 +169,48 @@ export function apply(ctx) {
|
|
|
108
169
|
else kept.push(m)
|
|
109
170
|
}
|
|
110
171
|
if (mode === null) return next()
|
|
111
|
-
payload.agent.session
|
|
172
|
+
const sid = payload.agent && payload.agent.session && payload.agent.session.id
|
|
173
|
+
if (typeof sid === 'string' && sid.length > 0) writeMode(sid, mode)
|
|
112
174
|
if (kept.length === 0) return { kind: 'reject' }
|
|
113
175
|
return { kind: 'enter', messages: kept }
|
|
114
176
|
})
|
|
115
177
|
|
|
116
|
-
// ---- badge
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
// UI, projection, and prompt-section all agree via the session log.
|
|
178
|
+
// ---- badge state read: GET /caveman/state?sessionId=... ----------------
|
|
179
|
+
// Fetch-mode badge client reads the per-session mode here (initial load
|
|
180
|
+
// plus a periodic re-sync). Exact route; the matcher strips the query.
|
|
120
181
|
const CYCLE = ['off', 'lite', 'full', 'ultra']
|
|
121
182
|
const webServer = ctx.get('webServer')
|
|
183
|
+
if (webServer !== undefined) {
|
|
184
|
+
try {
|
|
185
|
+
webServer.register({
|
|
186
|
+
kind: 'exact',
|
|
187
|
+
path: '/caveman/state',
|
|
188
|
+
handler: async (req, res) => {
|
|
189
|
+
if (req.method !== 'GET') {
|
|
190
|
+
res.writeHead(405)
|
|
191
|
+
res.end()
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
let sessionId = null
|
|
195
|
+
try {
|
|
196
|
+
sessionId = new URL(req.url || '/', 'http://x').searchParams.get('sessionId')
|
|
197
|
+
} catch (e) {
|
|
198
|
+
sessionId = null
|
|
199
|
+
}
|
|
200
|
+
const mode = typeof sessionId === 'string' && sessionId.length > 0 ? readMode(sessionId) : 'full'
|
|
201
|
+
res.writeHead(200, { 'content-type': 'application/json' })
|
|
202
|
+
res.end(JSON.stringify({ mode }))
|
|
203
|
+
},
|
|
204
|
+
})
|
|
205
|
+
} catch (e) {
|
|
206
|
+
console.error('[caveman] /caveman/state route registration failed:', e && e.message)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ---- badge click toggle: POST /caveman/toggle { sessionId } -------------
|
|
211
|
+
// Cycles off -> lite -> full -> ultra -> off through the sidecar (the same
|
|
212
|
+
// mutation /caveman performs) and returns the new value so the badge can
|
|
213
|
+
// self-update from the response.
|
|
122
214
|
if (webServer !== undefined) {
|
|
123
215
|
try {
|
|
124
216
|
webServer.register({
|
|
@@ -151,18 +243,9 @@ export function apply(ctx) {
|
|
|
151
243
|
res.end(JSON.stringify({ ok: false, error: 'session not found' }))
|
|
152
244
|
return
|
|
153
245
|
}
|
|
154
|
-
|
|
155
|
-
if (projections !== undefined) {
|
|
156
|
-
try {
|
|
157
|
-
const value = projections.snapshot(session).values.caveman
|
|
158
|
-
if (value && typeof value.mode === 'string') mode = value.mode
|
|
159
|
-
} catch (e) {
|
|
160
|
-
mode = 'full'
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
const at = CYCLE.indexOf(mode)
|
|
246
|
+
const at = CYCLE.indexOf(readMode(sessionId))
|
|
164
247
|
const next = CYCLE[(at === -1 ? 0 : at) + 1] === undefined ? CYCLE[0] : CYCLE[at + 1]
|
|
165
|
-
|
|
248
|
+
writeMode(sessionId, next)
|
|
166
249
|
res.writeHead(200, { 'content-type': 'application/json' })
|
|
167
250
|
res.end(JSON.stringify({ ok: true, mode: next }))
|
|
168
251
|
},
|
|
@@ -179,14 +262,9 @@ export function apply(ctx) {
|
|
|
179
262
|
order: 50,
|
|
180
263
|
text: (context) => {
|
|
181
264
|
const agent = context && context.agent
|
|
182
|
-
if (!agent
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const value = projections.snapshot(agent.session).values.caveman
|
|
186
|
-
if (value && typeof value.mode === 'string') mode = value.mode
|
|
187
|
-
} catch (err) {
|
|
188
|
-
mode = 'full'
|
|
189
|
-
}
|
|
265
|
+
if (!agent) return ''
|
|
266
|
+
const sid = agent.session && agent.session.id
|
|
267
|
+
const mode = typeof sid === 'string' && sid.length > 0 ? readMode(sid) : defaultMode
|
|
190
268
|
if (mode === 'off') return ''
|
|
191
269
|
const tag = mode === 'full' ? '[CAVEMAN]' : '[CAVEMAN:' + mode.toUpperCase() + ']'
|
|
192
270
|
return tag + ' Current user-set communication style. Follow the caveman skill rules at intensity "' + mode + '" for every response; this overrides any default in AGENTS.md.'
|
|
@@ -194,5 +272,5 @@ export function apply(ctx) {
|
|
|
194
272
|
})
|
|
195
273
|
}
|
|
196
274
|
|
|
197
|
-
console.log('[caveman] host active (
|
|
275
|
+
console.log('[caveman] host active (sidecar mode state + prompt section; writes no session events)')
|
|
198
276
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-caveman",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"./client": "./lib/client.js",
|
|
9
9
|
"./package.json": "./package.json"
|
|
10
10
|
},
|
|
11
|
-
"description": "Per-session caveman mode state
|
|
11
|
+
"description": "Per-session caveman mode state + composer dock badge + /caveman command + prompt-section injection. Writes NO session events: mode state lives in a $DSH_HOME/plugin-state sidecar keyed by session id (default from the 'caveman' settings namespace), and the badge reads GET /caveman/state (fetch mode). The KNOWN event-type registration is kept read-only for legacy sessions. Caveman skills ship from the deployment shared skill library (see ~/.dsh/skills), not from this package.",
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"keywords": [
|
|
14
14
|
"dsh",
|
|
@@ -23,9 +23,6 @@
|
|
|
23
23
|
"lib",
|
|
24
24
|
"cordis.patch.yml"
|
|
25
25
|
],
|
|
26
|
-
"dependencies": {
|
|
27
|
-
"zod": "^4.4.3"
|
|
28
|
-
},
|
|
29
26
|
"dsh": {
|
|
30
27
|
"bundle": {
|
|
31
28
|
"patch": "./cordis.patch.yml"
|