@try-works/dsh-righthand 0.1.19 → 0.1.21
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/index.d.ts +14 -0
- package/lib/client/index.d.ts.map +1 -0
- package/lib/client/index.js +118 -0
- package/lib/client/index.js.map +1 -0
- package/lib/client.js +373 -0
- package/lib/client.js.map +1 -0
- package/lib/files-tools.d.ts.map +1 -1
- package/lib/files-tools.js +1 -2
- package/lib/files-tools.js.map +1 -1
- package/lib/gui.d.ts +12 -0
- package/lib/gui.d.ts.map +1 -0
- package/lib/gui.js +159 -0
- package/lib/gui.js.map +1 -0
- package/lib/index.d.ts +3 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -1
- package/lib/index.js.map +1 -1
- package/lib/notify-tools.d.ts.map +1 -1
- package/lib/notify-tools.js +1 -2
- package/lib/notify-tools.js.map +1 -1
- package/lib/secrets-tools.d.ts.map +1 -1
- package/lib/secrets-tools.js +1 -2
- package/lib/secrets-tools.js.map +1 -1
- package/lib/text-tools.d.ts.map +1 -1
- package/lib/text-tools.js +2 -1
- package/lib/text-tools.js.map +1 -1
- package/package.json +26 -3
- package/src/client/index.ts +185 -0
- package/src/files-tools.ts +3 -4
- package/src/gui.ts +158 -0
- package/src/index.ts +4 -1
- package/src/notify-tools.ts +4 -4
- package/src/secrets-tools.ts +4 -3
- package/src/text-tools.ts +2 -1
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Righthand panel client half: a collapsible right-side panel inside the
|
|
3
|
+
* DSH Web GUI showing the plugin's live state - settings, store keys,
|
|
4
|
+
* tasks and events - and mutating it through the /righthand/* routes the
|
|
5
|
+
* host half (src/gui.ts) registers. Same-plugin GUI: no separate package.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
createElement as h,
|
|
10
|
+
useEffect,
|
|
11
|
+
useState,
|
|
12
|
+
type CSSProperties,
|
|
13
|
+
} from 'react'
|
|
14
|
+
import { createPortal } from 'react-dom'
|
|
15
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis'
|
|
16
|
+
// Type-only: declares `ctx.slots` (the client kernel's slot registry).
|
|
17
|
+
import type {} from '@deepseek-ai/dsh-cordis-client-runner/client'
|
|
18
|
+
import type {} from '@deepseek-ai/dsh-client-ui-layout/client'
|
|
19
|
+
|
|
20
|
+
/** Required services: the slot registry (client runtime). */
|
|
21
|
+
export const inject = ['slots']
|
|
22
|
+
|
|
23
|
+
const C = {
|
|
24
|
+
bg: '#15171a',
|
|
25
|
+
panel: '#1b1e23',
|
|
26
|
+
row: '#23272e',
|
|
27
|
+
border: '#2d323a',
|
|
28
|
+
text: '#e6e8ec',
|
|
29
|
+
dim: '#9aa3ad',
|
|
30
|
+
accent: '#3b82f6',
|
|
31
|
+
green: '#3fbf6e',
|
|
32
|
+
red: '#e5484d',
|
|
33
|
+
amber: '#d9a13b',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface State {
|
|
37
|
+
settings: Record<string, unknown>
|
|
38
|
+
storeKeys: string[]
|
|
39
|
+
tasks: any[]
|
|
40
|
+
events: any[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const EMPTY: State = { settings: {}, storeKeys: [], tasks: [], events: [] }
|
|
44
|
+
|
|
45
|
+
function fmt(s: unknown, n: number): string {
|
|
46
|
+
try { const t = JSON.stringify(s); return t === undefined ? '' : (t.length > n ? t.slice(0, n) + '...' : t) }
|
|
47
|
+
catch { return String(s) }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function Row(props: { label: string; value: string; tone?: string }): ReturnType<typeof h> {
|
|
51
|
+
return h('div', { style: { display: 'flex', gap: 8, justifyContent: 'space-between', padding: '4px 0', borderBottom: '1px solid ' + C.border, fontSize: 12 } },
|
|
52
|
+
h('span', { style: { color: C.dim, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, props.label),
|
|
53
|
+
h('span', { style: { color: props.tone ?? C.text, fontFamily: 'monospace', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '55%' } }, props.value),
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function Btn(props: { label: string; onClick: () => void; tone?: string; danger?: boolean }): ReturnType<typeof h> {
|
|
58
|
+
const base: CSSProperties = {
|
|
59
|
+
background: C.row, color: props.danger ? C.red : (props.tone ?? C.text),
|
|
60
|
+
border: '1px solid ' + C.border, borderRadius: 6, padding: '3px 10px',
|
|
61
|
+
fontSize: 11, cursor: 'pointer', whiteSpace: 'nowrap'
|
|
62
|
+
}
|
|
63
|
+
return h('button', { style: base, onClick: props.onClick }, props.label)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function Section(props: { title: string; children?: any }): ReturnType<typeof h> {
|
|
67
|
+
return h('div', { style: { marginTop: 14 } },
|
|
68
|
+
h('div', { style: { fontSize: 11, letterSpacing: '0.12em', textTransform: 'uppercase', color: C.dim, marginBottom: 6 } }, props.title),
|
|
69
|
+
props.children,
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function RighthandPanel(): ReturnType<typeof h> {
|
|
74
|
+
const [collapsed, setCollapsed] = useState(false)
|
|
75
|
+
const [state, setState] = useState<State>(EMPTY)
|
|
76
|
+
const [error, setError] = useState('')
|
|
77
|
+
const [patch, setPatch] = useState('{}')
|
|
78
|
+
const [newKey, setNewKey] = useState('')
|
|
79
|
+
const [newValue, setNewValue] = useState('')
|
|
80
|
+
|
|
81
|
+
const load = (): void => {
|
|
82
|
+
fetch('/righthand/state').then(r => r.json()).then((d: State) => { setState(d); setError('') })
|
|
83
|
+
.catch((e: Error) => setError(String(e)))
|
|
84
|
+
}
|
|
85
|
+
useEffect(() => { load() }, [])
|
|
86
|
+
|
|
87
|
+
const post = (path: string, body: unknown, then: () => void): void => {
|
|
88
|
+
fetch(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) })
|
|
89
|
+
.then(r => r.json()).then((d: any) => { if (!d.ok && d.error) setError(String(d.error)); then() })
|
|
90
|
+
.catch((e: Error) => setError(String(e)))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const railStyle: CSSProperties = {
|
|
94
|
+
position: 'fixed', right: 0, top: 0, bottom: 0, width: 44, background: C.panel,
|
|
95
|
+
borderLeft: '1px solid ' + C.border, display: 'flex', flexDirection: 'column',
|
|
96
|
+
alignItems: 'center', paddingTop: 12, gap: 10, zIndex: 9998
|
|
97
|
+
}
|
|
98
|
+
const panelStyle: CSSProperties = {
|
|
99
|
+
position: 'fixed', right: 0, top: 0, bottom: 0, width: 340, background: C.bg,
|
|
100
|
+
borderLeft: '1px solid ' + C.border, padding: 12, overflowY: 'auto', zIndex: 9998,
|
|
101
|
+
fontFamily: 'ui-sans-serif, system-ui, sans-serif', color: C.text, fontSize: 13
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const toggle = h('button', {
|
|
105
|
+
style: { background: 'none', border: 'none', color: C.dim, fontSize: 18, cursor: 'pointer' },
|
|
106
|
+
onClick: () => setCollapsed(!collapsed),
|
|
107
|
+
}, collapsed ? '<' : '>')
|
|
108
|
+
|
|
109
|
+
const refreshBtn = h(Btn, { label: 'Refresh', onClick: load })
|
|
110
|
+
|
|
111
|
+
if (collapsed) {
|
|
112
|
+
return createPortal(h('div', { style: railStyle, 'data-dsh-righthand-panel': 'collapsed' },
|
|
113
|
+
toggle,
|
|
114
|
+
h('span', { style: { color: C.green, fontSize: 10, width: 8, height: 8, borderRadius: '50%', background: C.green } }),
|
|
115
|
+
h('span', { style: { color: C.dim, fontSize: 11, writingMode: 'vertical-rl', letterSpacing: '0.12em', userSelect: 'none' } }, 'Righthand'),
|
|
116
|
+
), document.body)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return createPortal(h('div', { style: panelStyle, 'data-dsh-righthand-panel': 'expanded' },
|
|
120
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 } },
|
|
121
|
+
h('span', { style: { fontWeight: 600, letterSpacing: '0.05em' } }, 'Righthand'),
|
|
122
|
+
h('span', { style: { display: 'flex', gap: 6 } }, refreshBtn, toggle),
|
|
123
|
+
),
|
|
124
|
+
error !== '' ? h('div', { style: { color: C.red, fontSize: 11, marginBottom: 8 } }, error) : null,
|
|
125
|
+
|
|
126
|
+
h(Section, { title: 'Settings' },
|
|
127
|
+
Object.entries(state.settings).map(([k, v]) => h(Row, { key: k, label: k, value: fmt(v, 90) })),
|
|
128
|
+
h('div', { style: { display: 'flex', gap: 6, marginTop: 8 } },
|
|
129
|
+
h('input', {
|
|
130
|
+
value: patch, onChange: (e: any) => setPatch(e.target.value),
|
|
131
|
+
style: { flex: 1, background: C.row, color: C.text, border: '1px solid ' + C.border, borderRadius: 6, padding: '3px 8px', fontSize: 11, fontFamily: 'monospace' },
|
|
132
|
+
}),
|
|
133
|
+
h(Btn, { label: 'Patch', onClick: () => {
|
|
134
|
+
try { const p = JSON.parse(patch); post('/righthand/settings/set', { patch: p }, load) }
|
|
135
|
+
catch { setError('patch must be JSON') }
|
|
136
|
+
} }),
|
|
137
|
+
),
|
|
138
|
+
),
|
|
139
|
+
|
|
140
|
+
h(Section, { title: 'Store (' + state.storeKeys.length + ')' },
|
|
141
|
+
state.storeKeys.length === 0 ? h('div', { style: { color: C.dim, fontSize: 12 } }, '(empty)') : null,
|
|
142
|
+
state.storeKeys.map(k => h('div', { key: k, style: { display: 'flex', gap: 6, alignItems: 'center', padding: '2px 0' } },
|
|
143
|
+
h('span', { style: { flex: 1, fontFamily: 'monospace', fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, k),
|
|
144
|
+
h(Btn, { label: 'Delete', danger: true, onClick: () => post('/righthand/store/delete', { key: k }, load) }),
|
|
145
|
+
)),
|
|
146
|
+
h('div', { style: { display: 'flex', gap: 6, marginTop: 8 } },
|
|
147
|
+
h('input', { placeholder: 'key', value: newKey, onChange: (e: any) => setNewKey(e.target.value), style: { flex: 1, background: C.row, color: C.text, border: '1px solid ' + C.border, borderRadius: 6, padding: '3px 8px', fontSize: 11, fontFamily: 'monospace' } }),
|
|
148
|
+
h('input', { placeholder: 'json value', value: newValue, onChange: (e: any) => setNewValue(e.target.value), style: { flex: 1, background: C.row, color: C.text, border: '1px solid ' + C.border, borderRadius: 6, padding: '3px 8px', fontSize: 11, fontFamily: 'monospace' } }),
|
|
149
|
+
h(Btn, { label: 'Put', onClick: () => {
|
|
150
|
+
if (newKey === '') { setError('key required'); return }
|
|
151
|
+
try { const v = JSON.parse(newValue === '' ? 'null' : newValue); post('/righthand/store/put', { key: newKey, value: v }, () => { load(); setNewKey(''); setNewValue('') }) }
|
|
152
|
+
catch { setError('value must be JSON') }
|
|
153
|
+
} }),
|
|
154
|
+
),
|
|
155
|
+
),
|
|
156
|
+
|
|
157
|
+
h(Section, { title: 'Tasks (' + state.tasks.length + ')' },
|
|
158
|
+
state.tasks.length === 0 ? h('div', { style: { color: C.dim, fontSize: 12 } }, '(none)') : null,
|
|
159
|
+
state.tasks.map((t: any) => h('div', { key: t.id, style: { display: 'flex', gap: 6, alignItems: 'center', padding: '2px 0' } },
|
|
160
|
+
h('span', { style: { flex: 1, fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, (t.state === 'done' ? '[x] ' : t.state === 'failed' ? '[!] ' : '[ ] ') + t.title),
|
|
161
|
+
h(Btn, { label: 'Done', tone: C.green, onClick: () => post('/righthand/task/update', { id: t.id, state: 'done' }, load) }),
|
|
162
|
+
h(Btn, { label: 'Fail', danger: true, onClick: () => post('/righthand/task/update', { id: t.id, state: 'failed' }, load) }),
|
|
163
|
+
)),
|
|
164
|
+
),
|
|
165
|
+
|
|
166
|
+
h(Section, { title: 'Events (' + state.events.length + ')' },
|
|
167
|
+
state.events.length === 0 ? h('div', { style: { color: C.dim, fontSize: 12 } }, '(none)') : null,
|
|
168
|
+
state.events.map((e: any) => h('div', { key: e.id, style: { display: 'flex', gap: 6, alignItems: 'center', padding: '2px 0' } },
|
|
169
|
+
h('span', { style: { flex: 1, fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, e.title + ' @ ' + e.at + ' [' + e.state + ']'),
|
|
170
|
+
e.state === 'pending' ? h(Btn, { label: 'Cancel', danger: true, onClick: () => post('/righthand/event/cancel', { id: e.id }, load) }) : null,
|
|
171
|
+
)),
|
|
172
|
+
),
|
|
173
|
+
), document.body)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Client plugin body: register the panel into the frame-wide overlay slot.
|
|
178
|
+
*/
|
|
179
|
+
export function apply(ctx: ClientContext): void {
|
|
180
|
+
ctx.slots.inject('shell.overlay', () => ctx.slots.register({
|
|
181
|
+
name: 'shell.overlay',
|
|
182
|
+
id: 'righthand-panel',
|
|
183
|
+
order: 60,
|
|
184
|
+
}, RighthandPanel))
|
|
185
|
+
}
|
package/src/files-tools.ts
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
import type { Context } from '@deepseek-ai/cordis'
|
|
14
14
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
15
15
|
import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
|
16
|
-
|
|
16
|
+
// Type-only: declares `ctx.settings`.
|
|
17
|
+
import type {} from '@deepseek-ai/dsh-settings'
|
|
17
18
|
import { signRequest, presignGet } from './sigv4.ts'
|
|
18
19
|
import { genCall, genResult } from './cards.ts'
|
|
19
20
|
|
|
@@ -143,7 +144,7 @@ export function createR2Client(opts: R2Options) {
|
|
|
143
144
|
|
|
144
145
|
|
|
145
146
|
export function apply(ctx: Context, config: FilesConfig = {}): void {
|
|
146
|
-
const ns =
|
|
147
|
+
const ns = 'righthand'
|
|
147
148
|
|
|
148
149
|
/** Resolve accountId + credentials + bucket at call time. */
|
|
149
150
|
async function client(): Promise<ReturnType<typeof createR2Client>> {
|
|
@@ -301,5 +302,3 @@ export function apply(ctx: Context, config: FilesConfig = {}): void {
|
|
|
301
302
|
}
|
|
302
303
|
|
|
303
304
|
|
|
304
|
-
|
|
305
|
-
|
package/src/gui.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gui.ts - the righthand GUI host half: HTTP routes under /righthand/* on
|
|
3
|
+
* the DSH web server that read and mutate the same domains the tools use.
|
|
4
|
+
* The client half (src/client/index.ts) renders these as a panel inside the
|
|
5
|
+
* DSH Web GUI. Mounts only when a webServer exists (the Web surface);
|
|
6
|
+
* elsewhere the child fiber simply does not start.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
10
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
11
|
+
// Type-only: declares `ctx.settings`.
|
|
12
|
+
import type {} from '@deepseek-ai/dsh-settings'
|
|
13
|
+
import { storeDomain } from './store-tools.ts'
|
|
14
|
+
import { taskDomain } from './task-tools.ts'
|
|
15
|
+
import { eventsDomain } from './events-tools.ts'
|
|
16
|
+
// Type-only: activates the cordis Context merge for ctx.webServer.
|
|
17
|
+
import type {} from '@deepseek-ai/dsh-host-webserver'
|
|
18
|
+
|
|
19
|
+
export const name = 'righthand-gui'
|
|
20
|
+
export const inject = ['webServer', 'storageDomain', 'settings']
|
|
21
|
+
|
|
22
|
+
const ns = 'righthand'
|
|
23
|
+
|
|
24
|
+
function json(res: ServerResponse, code: number, body: unknown): void {
|
|
25
|
+
const text = JSON.stringify(body)
|
|
26
|
+
res.writeHead(code, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })
|
|
27
|
+
res.end(text)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readBody(req: IncomingMessage): Promise<string> {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const chunks: Buffer[] = []
|
|
33
|
+
let bytes = 0
|
|
34
|
+
req.on('data', (c: Buffer) => {
|
|
35
|
+
bytes += c.length
|
|
36
|
+
if (bytes > 65536) { reject(new Error('body too large')); req.destroy() }
|
|
37
|
+
else chunks.push(c)
|
|
38
|
+
})
|
|
39
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))
|
|
40
|
+
req.on('error', reject)
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function apply(ctx: Context): void {
|
|
45
|
+
let domains: { store: any; tasks: any; events: any } | undefined
|
|
46
|
+
let ready: Promise<void> | undefined
|
|
47
|
+
|
|
48
|
+
ctx.effect(() => {
|
|
49
|
+
ready = (async () => {
|
|
50
|
+
const [store, tasks, events] = await Promise.all([
|
|
51
|
+
ctx.storageDomain.open(storeDomain),
|
|
52
|
+
ctx.storageDomain.open(taskDomain),
|
|
53
|
+
ctx.storageDomain.open(eventsDomain),
|
|
54
|
+
])
|
|
55
|
+
domains = { store, tasks, events }
|
|
56
|
+
})()
|
|
57
|
+
return () => { }
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const ensure = async (): Promise<NonNullable<typeof domains>> => {
|
|
61
|
+
if (domains === undefined) { await ready }
|
|
62
|
+
if (domains === undefined) throw new Error('righthand domains not open')
|
|
63
|
+
return domains
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const getSettings = () => {
|
|
67
|
+
const v = ctx.settings.get(ns) as Record<string, unknown> | undefined
|
|
68
|
+
return v ?? {}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const disposers: Array<() => void> = []
|
|
72
|
+
ctx.effect(() => () => { for (const d of disposers.splice(0)) d() })
|
|
73
|
+
|
|
74
|
+
const route = (path: string, handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>): void => {
|
|
75
|
+
disposers.push(ctx.webServer.register({ kind: 'exact', path, handler }))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
route('/righthand/state', async (_req, res) => {
|
|
79
|
+
try {
|
|
80
|
+
const d = await ensure()
|
|
81
|
+
const storeKeys = [...d.store.table('rows').keys()]
|
|
82
|
+
const tasks = [...d.tasks.table('tasks').entries()]
|
|
83
|
+
const events = [...d.events.table('events').entries()]
|
|
84
|
+
json(res, 200, {
|
|
85
|
+
settings: getSettings(),
|
|
86
|
+
storeKeys,
|
|
87
|
+
tasks: tasks.map(([id, row]) => ({ id, ...row })),
|
|
88
|
+
events: events.map(([id, row]) => ({ id, ...row })),
|
|
89
|
+
})
|
|
90
|
+
} catch (e) {
|
|
91
|
+
json(res, 500, { error: String(e) })
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
route('/righthand/settings/set', async (req, res) => {
|
|
96
|
+
try {
|
|
97
|
+
const body = JSON.parse(await readBody(req))
|
|
98
|
+
await ctx.settings.update(ns, body.patch)
|
|
99
|
+
json(res, 200, { ok: true })
|
|
100
|
+
} catch (e) {
|
|
101
|
+
json(res, 400, { error: String(e) })
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
route('/righthand/store/put', async (req, res) => {
|
|
106
|
+
try {
|
|
107
|
+
const d = await ensure()
|
|
108
|
+
const body = JSON.parse(await readBody(req))
|
|
109
|
+
if (typeof body.key !== 'string' || body.key === '') throw new Error('key required')
|
|
110
|
+
await d.store.table('rows').put(body.key, { value: body.value ?? null, updatedAt: new Date().toISOString() })
|
|
111
|
+
const writes = d.store.global.get()
|
|
112
|
+
await d.store.global.set({ writes: writes.writes + 1 })
|
|
113
|
+
json(res, 200, { ok: true })
|
|
114
|
+
} catch (e) {
|
|
115
|
+
json(res, 400, { error: String(e) })
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
route('/righthand/store/delete', async (req, res) => {
|
|
120
|
+
try {
|
|
121
|
+
const d = await ensure()
|
|
122
|
+
const body = JSON.parse(await readBody(req))
|
|
123
|
+
const existed = await d.store.table('rows').delete(body.key)
|
|
124
|
+
json(res, 200, { ok: true, existed })
|
|
125
|
+
} catch (e) {
|
|
126
|
+
json(res, 400, { error: String(e) })
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
route('/righthand/task/update', async (req, res) => {
|
|
131
|
+
try {
|
|
132
|
+
const d = await ensure()
|
|
133
|
+
const body = JSON.parse(await readBody(req))
|
|
134
|
+
const row = await d.tasks.table('tasks').get(body.id)
|
|
135
|
+
if (row === undefined) { json(res, 404, { error: 'task not found' }); return }
|
|
136
|
+
const next: any = { ...row }
|
|
137
|
+
if (typeof body.state === 'string') next.state = body.state
|
|
138
|
+
if (body.result !== undefined) next.result = body.result
|
|
139
|
+
await d.tasks.table('tasks').put(body.id, next)
|
|
140
|
+
json(res, 200, { ok: true })
|
|
141
|
+
} catch (e) {
|
|
142
|
+
json(res, 400, { error: String(e) })
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
route('/righthand/event/cancel', async (req, res) => {
|
|
147
|
+
try {
|
|
148
|
+
const d = await ensure()
|
|
149
|
+
const body = JSON.parse(await readBody(req))
|
|
150
|
+
const row = await d.events.table('events').get(body.id)
|
|
151
|
+
if (row === undefined) { json(res, 404, { error: 'event not found' }); return }
|
|
152
|
+
await d.events.table('events').put(body.id, { ...row, state: 'cancelled' })
|
|
153
|
+
json(res, 200, { ok: true })
|
|
154
|
+
} catch (e) {
|
|
155
|
+
json(res, 400, { error: String(e) })
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* - notify-tools: rh_notify_send over ntfy.sh (keyless)
|
|
26
26
|
* - exec-tools: rh_run / rh_run_bg over ctx.subprocess + ctx.jobs
|
|
27
27
|
* - guard-tools: tools/pre-execute policy (config.rules)
|
|
28
|
+
* - gui: /righthand/* web routes + the client panel (src/client)
|
|
28
29
|
* - skills: the packaged dsh-righthand skill (ctx.skills)
|
|
29
30
|
*
|
|
30
31
|
* @module @try-works/dsh-righthand
|
|
@@ -42,6 +43,7 @@ import * as placesTools from './places-tools.ts'
|
|
|
42
43
|
import * as filesTools from './files-tools.ts'
|
|
43
44
|
import * as eventsTools from './events-tools.ts'
|
|
44
45
|
import * as notifyTools from './notify-tools.ts'
|
|
46
|
+
import * as guiTools from './gui.ts'
|
|
45
47
|
import * as righthandSkills from './skills.ts'
|
|
46
48
|
import type { GuardRule } from './guard-tools.ts'
|
|
47
49
|
|
|
@@ -73,12 +75,13 @@ export function apply(ctx: Context, config: RighthandConfig = {}): void {
|
|
|
73
75
|
ctx.plugin(filesTools)
|
|
74
76
|
ctx.plugin(eventsTools)
|
|
75
77
|
ctx.plugin(notifyTools)
|
|
78
|
+
ctx.plugin(guiTools)
|
|
76
79
|
ctx.plugin(guardTools, { rules: config.rules ?? [] })
|
|
77
80
|
ctx.plugin(righthandSkills)
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
// Individual modules stay importable for selective mounting.
|
|
81
|
-
export { storeTools, secretsTools, execTools, taskTools, textTools, weatherTools, placesTools, filesTools, eventsTools, notifyTools, guardTools, righthandSkills }
|
|
84
|
+
export { storeTools, secretsTools, execTools, taskTools, textTools, weatherTools, placesTools, filesTools, eventsTools, notifyTools, guiTools, guardTools, righthandSkills }
|
|
82
85
|
export { SKILL_NAME, SKILL_DESCRIPTION, skillDirectory, skillBody } from './skills.ts'
|
|
83
86
|
export type { GuardRule } from './guard-tools.ts'
|
|
84
87
|
export { guardFactsFor } from './guard-tools.ts'
|
package/src/notify-tools.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
import type { Context } from '@deepseek-ai/cordis'
|
|
10
10
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
11
|
-
|
|
11
|
+
// Type-only: declares `ctx.settings`.
|
|
12
|
+
import type {} from '@deepseek-ai/dsh-settings'
|
|
12
13
|
import { guardedFetch } from './weather-tools.ts'
|
|
13
14
|
import { genCall, genResult } from './cards.ts'
|
|
14
15
|
|
|
@@ -23,7 +24,7 @@ export interface NotifyConfig {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export function apply(ctx: Context, config: NotifyConfig = {}): void {
|
|
26
|
-
const ns =
|
|
27
|
+
const ns = 'righthand'
|
|
27
28
|
const baseUrl = config.baseUrl ?? 'https://ntfy.sh'
|
|
28
29
|
|
|
29
30
|
ctx.tools.register(defineTool({
|
|
@@ -61,5 +62,4 @@ export function apply(ctx: Context, config: NotifyConfig = {}): void {
|
|
|
61
62
|
return { topic, sent: true }
|
|
62
63
|
},
|
|
63
64
|
}))
|
|
64
|
-
}
|
|
65
|
-
|
|
65
|
+
}
|
package/src/secrets-tools.ts
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
import type { Context } from '@deepseek-ai/cordis'
|
|
11
11
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
12
12
|
import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
|
13
|
-
|
|
13
|
+
// Type-only: declares `ctx.settings` (the settings provider seam).
|
|
14
|
+
import type {} from '@deepseek-ai/dsh-settings'
|
|
14
15
|
import { genCall, genResult } from './cards.ts'
|
|
15
16
|
import z from '@deepseek-ai/schemastery'
|
|
16
17
|
|
|
@@ -34,7 +35,7 @@ export const righthandSettingsSchema = z.object({
|
|
|
34
35
|
|
|
35
36
|
/** Register the settings namespace + the secret/credential tools. */
|
|
36
37
|
export function apply(ctx: Context): void {
|
|
37
|
-
const ns =
|
|
38
|
+
const ns = 'righthand'
|
|
38
39
|
// Register once; the settings provider merges schema defaults + user doc.
|
|
39
40
|
const scope = ctx.settings.register(ns, righthandSettingsSchema)
|
|
40
41
|
|
|
@@ -157,4 +158,4 @@ export function apply(ctx: Context): void {
|
|
|
157
158
|
return { applied: true }
|
|
158
159
|
},
|
|
159
160
|
}))
|
|
160
|
-
}
|
|
161
|
+
}
|
package/src/text-tools.ts
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
import type { Context } from '@deepseek-ai/cordis'
|
|
11
11
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
12
|
-
import { BlockAssembler, createUserMessage
|
|
12
|
+
import { BlockAssembler, createUserMessage } from '@deepseek-ai/dsh-llm'
|
|
13
13
|
import type { GenerateOptions } from '@deepseek-ai/dsh-llm'
|
|
14
|
+
import { deepFreeze } from '@deepseek-ai/dsh-util-values'
|
|
14
15
|
import { genCall, genResult } from './cards.ts'
|
|
15
16
|
|
|
16
17
|
export const name = 'righthand-text'
|