dsh-caveman 0.1.4 → 0.1.6

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 CHANGED
@@ -6,6 +6,110 @@ window.__ModuleLoader__.load({
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  const React = require("react");
8
8
 
9
+ // ---- Settings → General row: caveman default mode --------------------
10
+ // Reads and writes the host's global default through /caveman/default
11
+ // (the settings-document namespace 'caveman'). New sessions start with
12
+ // this default; the badge still cycles the per-session value. Mirrors
13
+ // dsh-rtk's row — without this client-side slot registration the settings
14
+ // page shows no caveman entry at all (the host namespace alone is not
15
+ // enough; the panel enumerates slot items, not namespaces).
16
+ const CAVEMAN_MODES = [
17
+ "off",
18
+ "lite",
19
+ "full",
20
+ "ultra",
21
+ "wenyan-lite",
22
+ "wenyan-full",
23
+ "wenyan-ultra",
24
+ ];
25
+ function CavemanDefaultRow() {
26
+ const [mode, setMode] = React.useState(null);
27
+ React.useEffect(() => {
28
+ let cancelled = false;
29
+ fetch("/caveman/default")
30
+ .then((r) => (r.ok ? r.json() : null))
31
+ .then((data) => {
32
+ if (!cancelled && data && CAVEMAN_MODES.indexOf(data.defaultMode) !== -1) {
33
+ setMode(data.defaultMode);
34
+ }
35
+ })
36
+ .catch(() => {});
37
+ return () => {
38
+ cancelled = true;
39
+ };
40
+ }, []);
41
+ const change = (next) => {
42
+ fetch("/caveman/default", {
43
+ method: "POST",
44
+ headers: { "content-type": "application/json" },
45
+ body: JSON.stringify({ defaultMode: next }),
46
+ })
47
+ .then((r) => (r.ok ? r.json() : null))
48
+ .then((data) => {
49
+ if (data && data.ok) setMode(data.defaultMode);
50
+ })
51
+ .catch(() => {});
52
+ };
53
+ return React.createElement(
54
+ "div",
55
+ {
56
+ style: {
57
+ borderBottom: "1px solid var(--dsw-alias-border-l2)",
58
+ alignItems: "center",
59
+ gap: "8px",
60
+ padding: "16px 0",
61
+ display: "flex",
62
+ },
63
+ },
64
+ React.createElement(
65
+ "div",
66
+ {
67
+ style: {
68
+ flexDirection: "column",
69
+ flex: "1",
70
+ gap: "4px",
71
+ minWidth: "0",
72
+ paddingRight: "48px",
73
+ display: "flex",
74
+ },
75
+ },
76
+ React.createElement(
77
+ "div",
78
+ { style: { color: "var(--dsw-alias-label-primary)", fontSize: "14px", lineHeight: "22px" } },
79
+ "caveman 默认档位",
80
+ ),
81
+ React.createElement(
82
+ "div",
83
+ { style: { color: "var(--dsw-alias-label-tertiary)", fontSize: "12px", lineHeight: "18px" } },
84
+ "新会话默认档位(默认 full);徽章仍按会话切换",
85
+ ),
86
+ ),
87
+ React.createElement(
88
+ "select",
89
+ {
90
+ value: mode === null ? "" : mode,
91
+ disabled: mode === null,
92
+ onChange: (e) => change(e.target.value),
93
+ title: mode === null ? "loading" : mode,
94
+ style: {
95
+ background: "var(--dsw-alias-bg-module-platform)",
96
+ height: "36px",
97
+ color: "var(--dsw-alias-label-primary)",
98
+ cursor: "pointer",
99
+ border: "none",
100
+ borderRadius: "18px",
101
+ padding: "0 14px",
102
+ fontSize: "14px",
103
+ lineHeight: "22px",
104
+ },
105
+ },
106
+ mode === null
107
+ ? React.createElement("option", { value: "" }, "…")
108
+ : CAVEMAN_MODES.map((m) => React.createElement("option", { key: m, value: m }, m)),
109
+ ),
110
+ );
111
+ }
112
+
9
113
  // Badge display is centralized in dsh-badges: this plugin only registers
10
114
  // a badge descriptor (fetch-mode: initial GET + toggle-response self-
11
115
  // update + 5s re-sync). No session projection is involved; per-session
@@ -74,6 +178,24 @@ window.__ModuleLoader__.load({
74
178
  if (register()) return;
75
179
  Promise.resolve().then(() => tryRegister(500));
76
180
  });
181
+
182
+ // Settings → General row: registered when the settings section is
183
+ // declared (slots.inject defers until then). The row owns its own
184
+ // read/write via /caveman/default — no inject face needed.
185
+ ctx.slots.inject("settings.general.item", () => {
186
+ try {
187
+ ctx.slots.register(
188
+ {
189
+ name: "settings.general.item",
190
+ id: "caveman-default",
191
+ order: 11,
192
+ },
193
+ CavemanDefaultRow,
194
+ );
195
+ } catch (e) {
196
+ console.error("[dsh-caveman] settings row registration failed:", e);
197
+ }
198
+ });
77
199
  }
78
200
 
79
201
  exports.apply = apply;
package/lib/index.js CHANGED
@@ -79,8 +79,15 @@ export function apply(ctx) {
79
79
  // Sessions WITHOUT a sidecar entry follow this live default (full).
80
80
  let defaultMode = 'full'
81
81
  let cavemanScope = null
82
- const settings = ctx.get('settings')
83
- if (settings !== undefined) {
82
+ // `settings` is a cordis Service (`ctx.settings`) — reach it through an
83
+ // injection edge. `ctx.get('settings')` at apply time returns undefined on
84
+ // 0.1.2-rc.1 (the provider publishes asynchronously), which silently skipped
85
+ // this registration: the Settings row then read a live value while every
86
+ // write answered 503 "settings unavailable" (found + fixed 2026-09-12).
87
+ // Same pattern as @linxin666/dsh-pet / web-ui-settings: ctx.inject(['settings']).
88
+ ctx.inject(['settings'], (settingsCtx) => {
89
+ const settings = settingsCtx.settings
90
+ if (settings === undefined) return
84
91
  try {
85
92
  cavemanScope = settings.register(
86
93
  'caveman',
@@ -95,7 +102,7 @@ export function apply(ctx) {
95
102
  } catch (err) {
96
103
  console.warn('[caveman] settings namespace registration failed:', err && err.message)
97
104
  }
98
- }
105
+ })
99
106
 
100
107
  // ---- per-session mode state: sidecar JSON keyed by session id ----------
101
108
  // Truth source for the mode. Sessions WITHOUT an entry use the live
@@ -256,6 +263,67 @@ export function apply(ctx) {
256
263
  }
257
264
  }
258
265
 
266
+ // ---- Settings row read/write: GET/POST /caveman/default -----------------
267
+ // The Settings → General row ('caveman-default') reads and writes the global
268
+ // default mode through this route; all state lives in the settings document
269
+ // via the registered 'caveman' namespace (same shape as dsh-rtk's
270
+ // /rtk/default, which is the only reason rtk showed a row and caveman did
271
+ // not — the row is a CLIENT registration, not an automatic consequence of
272
+ // the namespace).
273
+ if (webServer !== undefined) {
274
+ try {
275
+ webServer.register({
276
+ kind: 'exact',
277
+ path: '/caveman/default',
278
+ handler: async (req, res) => {
279
+ const send = (status, body) => {
280
+ res.writeHead(status, { 'content-type': 'application/json' })
281
+ res.end(JSON.stringify(body))
282
+ }
283
+ if (req.method === 'GET') {
284
+ send(200, { defaultMode })
285
+ return
286
+ }
287
+ if (req.method !== 'POST') {
288
+ send(405, { ok: false, error: 'method not allowed' })
289
+ return
290
+ }
291
+ if (cavemanScope === null) {
292
+ send(503, { ok: false, error: 'settings unavailable' })
293
+ return
294
+ }
295
+ let raw = ''
296
+ try {
297
+ for await (const chunk of req) raw += chunk
298
+ } catch (e) {
299
+ send(400, { ok: false, error: 'bad body' })
300
+ return
301
+ }
302
+ let next
303
+ try {
304
+ next = JSON.parse(raw || '{}').defaultMode
305
+ } catch (e) {
306
+ next = undefined
307
+ }
308
+ if (typeof next !== 'string' || !VALID.includes(next)) {
309
+ send(400, { ok: false, error: 'defaultMode must be one of: ' + VALID.join(', ') })
310
+ return
311
+ }
312
+ try {
313
+ await cavemanScope.update({ defaultMode: next })
314
+ const stored = cavemanScope.get()
315
+ defaultMode = stored && VALID.includes(stored.defaultMode) ? stored.defaultMode : 'full'
316
+ send(200, { ok: true, defaultMode })
317
+ } catch (e) {
318
+ send(500, { ok: false, error: 'persist failed' })
319
+ }
320
+ },
321
+ })
322
+ } catch (e) {
323
+ console.error('[caveman] /caveman/default route registration failed:', e && e.message)
324
+ }
325
+ }
326
+
259
327
  const systemPrompt = ctx.get('systemPrompt')
260
328
  if (systemPrompt !== undefined) {
261
329
  systemPrompt.section({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-caveman",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
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 + 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. Compatible with @deepseek-ai/dsh-settings 0.1.2+: the namespace is passed as a literal string because the settingsNamespace brand helper was removed upstream (broken named import on 0.1.2-rc.1 boot).",
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. Compatible with @deepseek-ai/dsh-settings 0.1.2+: the namespace is passed as a literal string because the settingsNamespace brand helper was removed upstream (broken named import on 0.1.2-rc.1 boot). 0.1.5 adds the Settings -> General row (client slot settings.general.item + host route GET/POST /caveman/default) so the default mode is editable in the UI; a registered host namespace alone does NOT produce a settings row. 0.1.6 fixes the registration itself: reach the settings service through ctx.inject(['settings'], ...) instead of ctx.get('settings') at apply time (which returns undefined on 0.1.2-rc.1, so the namespace was never registered and every write to POST /caveman/default answered 503 (settings unavailable).",
12
12
  "license": "MIT",
13
13
  "keywords": [
14
14
  "dsh",