dsh-tacit 0.2.1 → 0.2.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/README.md +36 -2
- package/client/client.js +94 -27
- package/docs/README.md +3 -3
- package/docs/README.zh.md +6 -5
- package/lib/analyze.js +177 -21
- package/lib/index.js +13 -0
- package/lib/routes.js +1 -1
- package/lib/schema.js +17 -1
- package/lib/service.js +207 -55
- package/lib/store.js +1 -0
- package/package.json +13 -7
package/README.md
CHANGED
|
@@ -25,13 +25,21 @@ Tacit: learns → "The user often omits which app they mean — check apps/web
|
|
|
25
25
|
You never have to say it again.
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
<p align="center">
|
|
29
|
+
<img alt="Install Tacit, open its conversation tab, review learning controls, and edit the directive it gives the agent" src="https://raw.githubusercontent.com/hackernotfound/dsh-tacit/main/docs/assets/tacit-demo.gif" width="960">
|
|
30
|
+
</p>
|
|
31
|
+
|
|
28
32
|
## Install — 30 seconds
|
|
29
33
|
|
|
30
34
|
```bash
|
|
31
|
-
dsh plugin --profile web add dsh-tacit
|
|
35
|
+
npx @deepseek-ai/dsh plugin --profile web add dsh-tacit
|
|
32
36
|
```
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
If `dsh` is already installed globally, `dsh plugin --profile web add dsh-tacit`
|
|
39
|
+
is the equivalent shorthand.
|
|
40
|
+
|
|
41
|
+
Start (or restart) with `npx @deepseek-ai/dsh web`, then refresh the page.
|
|
42
|
+
Optional kick-start: **Settings → Tacit →
|
|
35
43
|
*Learn from my last 20 turns*** (≈ $0.02–0.05, once).
|
|
36
44
|
|
|
37
45
|
Requires DeepSeek Harness `>= 0.1.1-rc.1`, Node `>= 22`, and a DeepSeek API key
|
|
@@ -46,6 +54,11 @@ already configured in the harness (Tacit never reads it).
|
|
|
46
54
|
| **✨ Improve** | a composer button that rewrites your current draft using what Tacit has learned, with a before/after preview and 👍/👎 | $0.001–0.002 per click |
|
|
47
55
|
| **Measured, not guessed** | Settings shows your real trend — messy-turn rate and tokens per turn, first 20 turns vs. latest 20 | free |
|
|
48
56
|
|
|
57
|
+
<p align="center">
|
|
58
|
+
<img alt="Tacit settings with automatic learning controls and an editable active directive" src="https://raw.githubusercontent.com/hackernotfound/dsh-tacit/main/docs/assets/tacit-settings.png" width="960">
|
|
59
|
+
</p>
|
|
60
|
+
<p align="center"><sub>Settings → Tacit, captured from a clean local profile with a synthetic example directive.</sub></p>
|
|
61
|
+
|
|
49
62
|
## How it works
|
|
50
63
|
|
|
51
64
|
1. Tacit keeps a small, bounded digest of every turn: the prompt, what tools ran,
|
|
@@ -72,6 +85,27 @@ reports. Dollar figures are estimates at list price; a cost plugin such as
|
|
|
72
85
|
the honest list of limitations:
|
|
73
86
|
[Privacy, cost & limitations](https://github.com/hackernotfound/dsh-tacit/blob/main/docs/privacy-and-cost.md).
|
|
74
87
|
|
|
88
|
+
## Help shape Tacit
|
|
89
|
+
|
|
90
|
+
Tacit works and I use it every day — but so far it has learned from one
|
|
91
|
+
person's prompts. It gets better with more of them.
|
|
92
|
+
|
|
93
|
+
- **Try it and say what feels wrong.** A directive that misfires, a cost that
|
|
94
|
+
surprised you, a label that reads oddly:
|
|
95
|
+
[open an issue](https://github.com/hackernotfound/dsh-tacit/issues/new/choose).
|
|
96
|
+
Two minutes of your time beats a week of my guessing.
|
|
97
|
+
- **Pick up a task.** Issues tagged
|
|
98
|
+
[`good first issue`](https://github.com/hackernotfound/dsh-tacit/labels/good%20first%20issue)
|
|
99
|
+
are small and come with acceptance criteria;
|
|
100
|
+
[`help wanted`](https://github.com/hackernotfound/dsh-tacit/labels/help%20wanted)
|
|
101
|
+
ones are bigger and I will pair on the design.
|
|
102
|
+
- **Ask anything** in
|
|
103
|
+
[Discussions](https://github.com/hackernotfound/dsh-tacit/discussions).
|
|
104
|
+
|
|
105
|
+
PRs are welcome — the fork → branch → PR walkthrough is in
|
|
106
|
+
[CONTRIBUTING.md](https://github.com/hackernotfound/dsh-tacit/blob/main/CONTRIBUTING.md).
|
|
107
|
+
No API key is needed to run the tests.
|
|
108
|
+
|
|
75
109
|
## Documentation
|
|
76
110
|
|
|
77
111
|
| | |
|
package/client/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
// Copyright (c) 2026 hackernotfound — https://github.com/hackernotfound/dsh-tacit
|
|
3
|
+
// GENERATED by scripts/build-client.mjs from client/src/ — edit the sources, not this file.
|
|
3
4
|
/**
|
|
4
5
|
* dsh-tacit — Client half (installed package bundle entry, zero-build).
|
|
5
6
|
*
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
* slot, beside Chat/Trajectory) listing every turn's digest with an
|
|
13
14
|
* Analyze button and the analysis report;
|
|
14
15
|
* - a small ✨ Improve button in the composer tool row
|
|
15
|
-
* (`conversation.input.left`)
|
|
16
|
+
* (`conversation.input.left`) while it is enabled in Settings;
|
|
16
17
|
* - a before/after preview popup in `conversation.input.overlay` whose
|
|
17
18
|
* Apply action replaces the composer draft via `inputActions.setDraft`.
|
|
18
19
|
*
|
|
@@ -57,9 +58,13 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
57
58
|
'trigger.correction': '纠正',
|
|
58
59
|
'trigger.manual': '手动',
|
|
59
60
|
'trigger.bootstrap': '引导',
|
|
61
|
+
'trigger.good': '做对了',
|
|
62
|
+
'report.strengths': '这次做对了什么',
|
|
63
|
+
'report.lesson': '经验',
|
|
64
|
+
'settings.learnGood': '也从混乱之后的顺利提示词中学习',
|
|
60
65
|
'bootstrap.btn': '从我最近 20 轮中学习',
|
|
61
66
|
'bootstrap.running': '学习中… {done}/{total}',
|
|
62
|
-
'bootstrap.hint': '
|
|
67
|
+
'bootstrap.hint': '约 $0.02–0.05,一次性。已顺利完成的提示词会被跳过。',
|
|
63
68
|
'steer.enrich': '发送前补充学到的上下文(实验性,每次发送一次小调用)',
|
|
64
69
|
'steer.title': '智能体被告知的关于你的信息',
|
|
65
70
|
'steer.desc': '这些指令会注入每个新会话的系统提示词,让智能体替你补上你通常没说的内容。',
|
|
@@ -74,6 +79,9 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
74
79
|
'steer.active': '已生效',
|
|
75
80
|
'steer.retired': '已淘汰 · {reason}',
|
|
76
81
|
'steer.user': '你添加的',
|
|
82
|
+
'steer.workspace': '仅 {name}',
|
|
83
|
+
'steer.scope': '适用范围',
|
|
84
|
+
'steer.everywhere': '所有工作区',
|
|
77
85
|
'settings.auto': '自动分析混乱轮次',
|
|
78
86
|
'settings.budget': '每日自动分析上限',
|
|
79
87
|
settings: '设置',
|
|
@@ -164,9 +172,13 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
164
172
|
'trigger.correction': 'correction',
|
|
165
173
|
'trigger.manual': 'manual',
|
|
166
174
|
'trigger.bootstrap': 'bootstrap',
|
|
175
|
+
'trigger.good': 'what worked',
|
|
176
|
+
'report.strengths': 'What this prompt got right',
|
|
177
|
+
'report.lesson': 'Lesson',
|
|
178
|
+
'settings.learnGood': 'Also learn from a clean prompt right after a messy turn',
|
|
167
179
|
'bootstrap.btn': 'Learn from my last 20 turns',
|
|
168
180
|
'bootstrap.running': 'Learning… {done}/{total}',
|
|
169
|
-
'bootstrap.hint': '≈ $0.02, one time. Skips prompts that already went fine on their own.',
|
|
181
|
+
'bootstrap.hint': '≈ $0.02–0.05, one time. Skips prompts that already went fine on their own.',
|
|
170
182
|
'steer.enrich': 'Add learned context before each send (experimental — one small call per send)',
|
|
171
183
|
'steer.title': 'What the agent is told about you',
|
|
172
184
|
'steer.desc': 'These directives ride every new session\'s system prompt so the agent fills in what you usually leave unsaid.',
|
|
@@ -181,6 +193,9 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
181
193
|
'steer.active': 'active',
|
|
182
194
|
'steer.retired': 'retired · {reason}',
|
|
183
195
|
'steer.user': 'yours',
|
|
196
|
+
'steer.workspace': 'only {name}',
|
|
197
|
+
'steer.scope': 'Applies to',
|
|
198
|
+
'steer.everywhere': 'Everywhere',
|
|
184
199
|
'settings.auto': 'Auto-analyze messy turns',
|
|
185
200
|
'settings.budget': 'Daily cap on automatic analyses',
|
|
186
201
|
settings: 'Settings',
|
|
@@ -346,13 +361,14 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
346
361
|
return version
|
|
347
362
|
}
|
|
348
363
|
|
|
349
|
-
// ── Root store (
|
|
364
|
+
// ── Root store (settings section + bootstrap + directives editor) ──────
|
|
350
365
|
|
|
351
366
|
const rootStore = {
|
|
352
367
|
config: null,
|
|
353
368
|
profile: null,
|
|
354
369
|
auto: null,
|
|
355
370
|
steering: null, // {enabled, text}
|
|
371
|
+
workspaces: [], // [{cwd, label}] from /state
|
|
356
372
|
trend: null, // measured early-vs-recent trend
|
|
357
373
|
bootstrap: null, // {running, done, total}
|
|
358
374
|
coached: [], // cross-session coached-prompt entries
|
|
@@ -388,6 +404,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
388
404
|
rootStore.profile = state.profile !== null && typeof state.profile === 'object' ? state.profile : null
|
|
389
405
|
rootStore.auto = state.auto !== null && typeof state.auto === 'object' ? state.auto : null
|
|
390
406
|
rootStore.steering = state.steering !== null && typeof state.steering === 'object' ? state.steering : null
|
|
407
|
+
rootStore.workspaces = Array.isArray(state.workspaces) ? state.workspaces : []
|
|
391
408
|
rootStore.bootstrap = state.bootstrap !== null && typeof state.bootstrap === 'object' ? state.bootstrap : null
|
|
392
409
|
}
|
|
393
410
|
const coached = await api('/history', { limit: 50 })
|
|
@@ -415,6 +432,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
415
432
|
rootStore.profile = state.profile !== null && typeof state.profile === 'object' ? state.profile : null
|
|
416
433
|
rootStore.auto = state.auto !== null && typeof state.auto === 'object' ? state.auto : null
|
|
417
434
|
rootStore.steering = state.steering !== null && typeof state.steering === 'object' ? state.steering : null
|
|
435
|
+
rootStore.workspaces = Array.isArray(state.workspaces) ? state.workspaces : []
|
|
418
436
|
rootStore.bootstrap = state.bootstrap !== null && typeof state.bootstrap === 'object' ? state.bootstrap : null
|
|
419
437
|
}
|
|
420
438
|
} catch {
|
|
@@ -944,14 +962,21 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
944
962
|
}
|
|
945
963
|
return h('div', { className: 'tacit-report' },
|
|
946
964
|
h('div', { className: 'tacit-report-title' },
|
|
947
|
-
t('report.problems'),
|
|
965
|
+
report.trigger === 'good' ? t('report.strengths') : t('report.problems'),
|
|
948
966
|
' ',
|
|
949
|
-
h('span', { className: 'tacit-chip tacit-chip-trigger' }, t('trigger.' + report.trigger))),
|
|
950
|
-
report.
|
|
951
|
-
?
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
967
|
+
h('span', { className: 'tacit-chip tacit-chip-trigger' + (report.trigger === 'good' ? ' tacit-chip-ok' : '') }, t('trigger.' + report.trigger))),
|
|
968
|
+
report.trigger === 'good'
|
|
969
|
+
? (Array.isArray(report.strengths) && report.strengths.length > 0
|
|
970
|
+
? report.strengths.map((strength, index) => h('div', { key: index, className: 'tacit-problem' },
|
|
971
|
+
h('span', { className: 'tacit-chip tacit-chip-ok' }, String(strength.kind)),
|
|
972
|
+
' ',
|
|
973
|
+
h('span', { className: 'tacit-problem-what' }, String(strength.what))))
|
|
974
|
+
: h('div', { className: 'tacit-report-note' }, '—'))
|
|
975
|
+
: report.problems.length === 0
|
|
976
|
+
? h('div', { className: 'tacit-report-note' }, '—')
|
|
977
|
+
: report.problems.map((problem, index) => ProblemRow(kit, problem, index)),
|
|
978
|
+
report.trigger === 'good' ? null : h('div', { className: 'tacit-report-title' }, t('report.improved')),
|
|
979
|
+
report.trigger === 'good' ? null : improved.length > 0
|
|
955
980
|
? h('div', { className: 'tacit-report-improved' },
|
|
956
981
|
h('div', { className: 'tacit-report-actions' },
|
|
957
982
|
h('button', {
|
|
@@ -966,7 +991,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
966
991
|
: h('div', { className: 'tacit-report-note' }, t('report.emptyImproved')),
|
|
967
992
|
typeof report.explanation === 'string' && report.explanation.length > 0
|
|
968
993
|
? h('div', null,
|
|
969
|
-
h('div', { className: 'tacit-report-title' }, t('report.explanation')),
|
|
994
|
+
h('div', { className: 'tacit-report-title' }, report.trigger === 'good' ? t('report.lesson') : t('report.explanation')),
|
|
970
995
|
h('div', { className: 'tacit-report-body' },
|
|
971
996
|
MarkdownText !== null
|
|
972
997
|
? h(MarkdownText, { text: report.explanation })
|
|
@@ -1063,6 +1088,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1063
1088
|
const [model, setModel] = useState(config !== null && typeof config.model === 'string' ? config.model : 'deepseek-v4-flash')
|
|
1064
1089
|
const [budgetText, setBudgetText] = useState(config !== null && typeof config.autoDailyBudget === 'number' ? String(config.autoDailyBudget) : '30')
|
|
1065
1090
|
const [auto, setAuto] = useState(config !== null && config.autoAnalyze !== false)
|
|
1091
|
+
const [learnGood, setLearnGood] = useState(config !== null && config.learnFromGood !== false)
|
|
1066
1092
|
const [live, setLive] = useState(config !== null && config.liveSuggestions !== false)
|
|
1067
1093
|
const [notice, setNotice] = useState(null)
|
|
1068
1094
|
|
|
@@ -1071,6 +1097,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1071
1097
|
if (typeof config.model === 'string') setModel(config.model)
|
|
1072
1098
|
if (typeof config.autoDailyBudget === 'number') setBudgetText(String(config.autoDailyBudget))
|
|
1073
1099
|
if (typeof config.autoAnalyze === 'boolean') setAuto(config.autoAnalyze)
|
|
1100
|
+
if (typeof config.learnFromGood === 'boolean') setLearnGood(config.learnFromGood)
|
|
1074
1101
|
if (typeof config.liveSuggestions === 'boolean') setLive(config.liveSuggestions)
|
|
1075
1102
|
}
|
|
1076
1103
|
}, [config])
|
|
@@ -1084,6 +1111,11 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1084
1111
|
setBudgetText(String(number))
|
|
1085
1112
|
updateConfig(store, { autoDailyBudget: number })
|
|
1086
1113
|
}
|
|
1114
|
+
const toggleLearnGood = () => {
|
|
1115
|
+
const next = !learnGood
|
|
1116
|
+
setLearnGood(next)
|
|
1117
|
+
updateConfig(store, { learnFromGood: next })
|
|
1118
|
+
}
|
|
1087
1119
|
const toggleAuto = () => {
|
|
1088
1120
|
const next = !auto
|
|
1089
1121
|
setAuto(next)
|
|
@@ -1116,6 +1148,9 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1116
1148
|
h('div', { className: 'tacit-settings-row' },
|
|
1117
1149
|
h('label', { className: 'tacit-settings-label' }, t('settings.auto')),
|
|
1118
1150
|
h('input', { type: 'checkbox', checked: auto, onChange: toggleAuto })),
|
|
1151
|
+
h('div', { className: 'tacit-settings-row' },
|
|
1152
|
+
h('label', { className: 'tacit-settings-label' }, t('settings.learnGood')),
|
|
1153
|
+
h('input', { type: 'checkbox', checked: learnGood, onChange: toggleLearnGood })),
|
|
1119
1154
|
h('div', { className: 'tacit-settings-row' },
|
|
1120
1155
|
h('label', { className: 'tacit-settings-label' }, t('settings.budget')),
|
|
1121
1156
|
h('input', {
|
|
@@ -1220,8 +1255,9 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1220
1255
|
function PreviewOverlay(kit) {
|
|
1221
1256
|
const { t } = kit
|
|
1222
1257
|
return function PreviewOverlayView(props) {
|
|
1223
|
-
const
|
|
1224
|
-
|
|
1258
|
+
const store = props.tacitStore !== null && typeof props.tacitStore === 'object'
|
|
1259
|
+
? props.tacitStore
|
|
1260
|
+
: null
|
|
1225
1261
|
useStoreVersion(store)
|
|
1226
1262
|
if (store === null || !store.preview.open) return null
|
|
1227
1263
|
|
|
@@ -1258,7 +1294,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1258
1294
|
}
|
|
1259
1295
|
}
|
|
1260
1296
|
|
|
1261
|
-
// ── Feedback strip (
|
|
1297
|
+
// ── Feedback strip (above the composer, Improve enabled only) ───────────
|
|
1262
1298
|
|
|
1263
1299
|
function FeedbackStrip(kit) {
|
|
1264
1300
|
const { t } = kit
|
|
@@ -1328,13 +1364,19 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1328
1364
|
}
|
|
1329
1365
|
}
|
|
1330
1366
|
|
|
1331
|
-
// ── Global panel (
|
|
1367
|
+
// ── Global panel (Settings → Tacit section) ────────────────────────────
|
|
1332
1368
|
|
|
1333
1369
|
function DirectivesEditor(kit) {
|
|
1334
1370
|
const { t } = kit
|
|
1335
1371
|
return function DirectivesEditorView(props) {
|
|
1336
1372
|
const { config, directives, steering } = props
|
|
1373
|
+
const workspaces = Array.isArray(props.workspaces) ? props.workspaces : []
|
|
1337
1374
|
const [draft, setDraft] = useState('')
|
|
1375
|
+
const [scope, setScope] = useState('')
|
|
1376
|
+
const labelOf = (cwd) => {
|
|
1377
|
+
const parts = String(cwd).split(/[\\/]+/).filter((part) => part.length > 0)
|
|
1378
|
+
return parts.length > 0 ? parts[parts.length - 1] : String(cwd)
|
|
1379
|
+
}
|
|
1338
1380
|
const [steerOn, setSteerOn] = useState(config !== null && config.steerAgent !== false)
|
|
1339
1381
|
const [enrichOn, setEnrichOn] = useState(config !== null && config.enrichPrompts === true)
|
|
1340
1382
|
useEffect(() => {
|
|
@@ -1355,7 +1397,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1355
1397
|
const text = draft.trim()
|
|
1356
1398
|
if (text.length === 0) return
|
|
1357
1399
|
setDraft('')
|
|
1358
|
-
editDirectives({ action: 'add', text: text.slice(0, 300) })
|
|
1400
|
+
editDirectives({ action: 'add', text: text.slice(0, 300), ...(scope.length > 0 ? { workspace: scope } : {}) })
|
|
1359
1401
|
}
|
|
1360
1402
|
return h('div', { className: 'tacit-panel-section', 'data-testid': 'tacit-steering' },
|
|
1361
1403
|
h('div', { className: 'tacit-report-title' }, t('steer.title')),
|
|
@@ -1379,6 +1421,9 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1379
1421
|
}),
|
|
1380
1422
|
h('span', { className: 'tacit-directive-text' }, String(entry.text)),
|
|
1381
1423
|
h('span', { className: 'tacit-chip' }, entry.source === 'user' ? t('steer.user') : t('steer.distilled')),
|
|
1424
|
+
typeof entry.workspace === 'string' && entry.workspace.length > 0
|
|
1425
|
+
? h('span', { className: 'tacit-chip tacit-chip-scope', title: entry.workspace }, t('steer.workspace', { name: labelOf(entry.workspace) }))
|
|
1426
|
+
: null,
|
|
1382
1427
|
entry.status === 'candidate'
|
|
1383
1428
|
? h('span', { className: 'tacit-chip tacit-chip-trial' }, t('steer.trial', {
|
|
1384
1429
|
n: String(entry.trial !== null && typeof entry.trial === 'object' && typeof entry.trial.turns === 'number' ? entry.trial.turns : 0),
|
|
@@ -1401,6 +1446,16 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1401
1446
|
if (event.key === 'Enter') onAdd()
|
|
1402
1447
|
},
|
|
1403
1448
|
}),
|
|
1449
|
+
workspaces.length > 0
|
|
1450
|
+
? h('select', {
|
|
1451
|
+
className: 'tacit-input tacit-select',
|
|
1452
|
+
'aria-label': t('steer.scope'),
|
|
1453
|
+
value: scope,
|
|
1454
|
+
onChange: (event) => setScope(event.target.value),
|
|
1455
|
+
},
|
|
1456
|
+
h('option', { value: '' }, t('steer.everywhere')),
|
|
1457
|
+
...workspaces.map((entry) => h('option', { key: entry.cwd, value: entry.cwd }, entry.label)))
|
|
1458
|
+
: null,
|
|
1404
1459
|
h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm', disabled: draft.trim().length === 0, onClick: onAdd }, t('steer.add'))),
|
|
1405
1460
|
steering !== null && typeof steering === 'object' && typeof steering.text === 'string' && steering.text.length > 0
|
|
1406
1461
|
? h('details', { className: 'tacit-preview' },
|
|
@@ -1424,6 +1479,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1424
1479
|
const [model, setModel] = useState(config !== null && typeof config.model === 'string' ? config.model : 'deepseek-v4-flash')
|
|
1425
1480
|
const [budgetText, setBudgetText] = useState(config !== null && typeof config.autoDailyBudget === 'number' ? String(config.autoDailyBudget) : '30')
|
|
1426
1481
|
const [auto, setAuto] = useState(config !== null && config.autoAnalyze !== false)
|
|
1482
|
+
const [learnGood, setLearnGood] = useState(config !== null && config.learnFromGood !== false)
|
|
1427
1483
|
const [live, setLive] = useState(config !== null && config.liveSuggestions !== false)
|
|
1428
1484
|
|
|
1429
1485
|
useEffect(() => {
|
|
@@ -1431,6 +1487,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1431
1487
|
if (typeof config.model === 'string') setModel(config.model)
|
|
1432
1488
|
if (typeof config.autoDailyBudget === 'number') setBudgetText(String(config.autoDailyBudget))
|
|
1433
1489
|
if (typeof config.autoAnalyze === 'boolean') setAuto(config.autoAnalyze)
|
|
1490
|
+
if (typeof config.learnFromGood === 'boolean') setLearnGood(config.learnFromGood)
|
|
1434
1491
|
if (typeof config.liveSuggestions === 'boolean') setLive(config.liveSuggestions)
|
|
1435
1492
|
}
|
|
1436
1493
|
}, [config])
|
|
@@ -1444,6 +1501,11 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1444
1501
|
setBudgetText(String(number))
|
|
1445
1502
|
updateRootConfig({ autoDailyBudget: number })
|
|
1446
1503
|
}
|
|
1504
|
+
const toggleLearnGood = () => {
|
|
1505
|
+
const next = !learnGood
|
|
1506
|
+
setLearnGood(next)
|
|
1507
|
+
updateRootConfig({ learnFromGood: next })
|
|
1508
|
+
}
|
|
1447
1509
|
const toggleAuto = () => {
|
|
1448
1510
|
const next = !auto
|
|
1449
1511
|
setAuto(next)
|
|
@@ -1483,6 +1545,9 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1483
1545
|
h('div', { className: 'tacit-settings-row' },
|
|
1484
1546
|
h('label', { className: 'tacit-settings-label' }, t('settings.auto')),
|
|
1485
1547
|
h('input', { type: 'checkbox', checked: auto, onChange: toggleAuto })),
|
|
1548
|
+
h('div', { className: 'tacit-settings-row' },
|
|
1549
|
+
h('label', { className: 'tacit-settings-label' }, t('settings.learnGood')),
|
|
1550
|
+
h('input', { type: 'checkbox', checked: learnGood, onChange: toggleLearnGood })),
|
|
1486
1551
|
h('div', { className: 'tacit-settings-row' },
|
|
1487
1552
|
h('label', { className: 'tacit-settings-label' }, t('settings.budget')),
|
|
1488
1553
|
h('input', {
|
|
@@ -1501,7 +1566,8 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1501
1566
|
error !== null && typeof error === 'object'
|
|
1502
1567
|
? h('div', { className: 'tacit-error' }, t('err.' + String(error.code), { detail: String(error.detail || '') }))
|
|
1503
1568
|
: null,
|
|
1504
|
-
h(DirectivesEditorView, {
|
|
1569
|
+
h(DirectivesEditorView, {
|
|
1570
|
+
workspaces: rootStore.workspaces, config, directives, steering: rootStore.steering }),
|
|
1505
1571
|
h('div', { className: 'tacit-panel-section' },
|
|
1506
1572
|
h('div', { className: 'tacit-report-title' }, t('panel.styleRules')),
|
|
1507
1573
|
styleRules.length === 0
|
|
@@ -1593,13 +1659,13 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1593
1659
|
+ '.tacit-modal-col-title{font-size:11px;font-weight:600;color:var(--dsw-alias-label-secondary);margin-bottom:6px}'
|
|
1594
1660
|
+ '.tacit-modal-rationale-text{font-size:12px;color:var(--dsw-alias-label-primary);line-height:1.6}.tacit-modal-savings{color:var(--dsw-alias-state-success-primary);font-size:12px;font-weight:600}'
|
|
1595
1661
|
+ '.tacit-modal-actions{display:flex;gap:8px;justify-content:flex-end}'
|
|
1596
|
-
+ '.tacit-feedback{display:flex;justify-content:center;align-items:center;margin:
|
|
1662
|
+
+ '.tacit-feedback{display:flex;justify-content:center;align-items:center;margin:0 2px 8px;font-size:12px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:6px 10px;gap:8px;transition:opacity .35s ease}.tacit-feedback-fading{opacity:0}'
|
|
1597
1663
|
+ '.tacit-feedback-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.tacit-feedback-title{color:var(--dsw-alias-label-secondary)}'
|
|
1598
1664
|
+ '.tacit-feedback-vote{font:inherit;font-size:14px;line-height:1;cursor:pointer;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:999px;padding:3px 8px}.tacit-feedback-vote:hover{border-color:var(--dsw-alias-brand-primary)}.tacit-feedback-vote-active{border-color:var(--dsw-alias-state-warn-primary);background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent)}'
|
|
1599
1665
|
+ '.tacit-feedback-reason{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.tacit-feedback-input{width:260px;max-width:60vw}'
|
|
1600
1666
|
+ '.tacit-feedback-noted{color:var(--dsw-alias-state-success-primary);font-weight:600}'
|
|
1601
1667
|
+ '.tacit-trend{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:6px}.tacit-chip-trigger,.tacit-coached-trigger{text-transform:uppercase;letter-spacing:.04em;font-size:10px}.tacit-coached-trigger{margin-left:auto}.tacit-row-enrichment{margin-top:8px;border-left:3px solid var(--dsw-alias-brand-primary);padding:4px 10px;background:var(--dsw-alias-bg-layer-2);border-radius:6px}'
|
|
1602
|
-
+ '.tacit-chip-trial{color:var(--dsw-alias-brand-primary)}.tacit-chip-ok{color:var(--dsw-alias-state-success-primary)}'
|
|
1668
|
+
+ '.tacit-chip-trial{color:var(--dsw-alias-brand-primary)}.tacit-chip-ok{color:var(--dsw-alias-state-success-primary)}.tacit-chip-scope{color:var(--dsw-alias-brand-primary)}.tacit-select{max-width:11rem}'
|
|
1603
1669
|
+ '.tacit-directive{display:flex;align-items:center;gap:8px}.tacit-directive-text{flex:1;min-width:0}.tacit-directive-off .tacit-directive-text{opacity:.5;text-decoration:line-through}.tacit-directive-input{width:min(420px,100%);flex:1}.tacit-preview summary{cursor:pointer;font-size:11px;color:var(--dsw-alias-label-secondary)}.tacit-preview pre{margin-top:6px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:6px;padding:8px 10px}'
|
|
1604
1670
|
+ '.tacit-rules-list{display:flex;flex-direction:column;gap:6px}.tacit-rule{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-left:3px solid var(--dsw-alias-brand-primary);border-radius:6px;padding:6px 10px;font-size:12px;line-height:1.55;color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word}'
|
|
1605
1671
|
|
|
@@ -1646,14 +1712,15 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1646
1712
|
id: 'tacit-preview',
|
|
1647
1713
|
order: 10,
|
|
1648
1714
|
locale: NS,
|
|
1649
|
-
inject: (sessionId) => ({
|
|
1715
|
+
inject: (sessionId) => ({ tacitStore: storeFor(sessionId) }),
|
|
1650
1716
|
}, (props) => h(PreviewOverlayView, props)))
|
|
1651
1717
|
|
|
1652
|
-
// The post-apply 👍/👎 strip lives in the
|
|
1653
|
-
// (
|
|
1718
|
+
// The post-apply 👍/👎 strip lives in the full-width row ABOVE the composer
|
|
1719
|
+
// card (input.dock). Not composer.dock: the harness hides that one until a
|
|
1720
|
+
// conversation has content, which is exactly when a first draft is improved.
|
|
1654
1721
|
const FeedbackStripView = FeedbackStrip(kit)
|
|
1655
|
-
ctx.slots.inject('conversation.
|
|
1656
|
-
name: 'conversation.
|
|
1722
|
+
ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({
|
|
1723
|
+
name: 'conversation.input.dock',
|
|
1657
1724
|
id: 'tacit-feedback',
|
|
1658
1725
|
order: 10,
|
|
1659
1726
|
locale: NS,
|
|
@@ -1661,8 +1728,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1661
1728
|
}, (props) => h(FeedbackStripView, props)))
|
|
1662
1729
|
|
|
1663
1730
|
// Own Settings page under "Cost": a nav section in the Settings panel,
|
|
1664
|
-
// on its own. Reuses the coach panel
|
|
1665
|
-
// clear reports, coached prompts). Order 32 places it right after
|
|
1731
|
+
// on its own. Reuses the coach panel. Order 32 places it right after
|
|
1666
1732
|
// cost-meter's "Cost" section (order 30/31).
|
|
1667
1733
|
const SettingsSectionView = SettingsSection(kit)
|
|
1668
1734
|
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
@@ -1682,6 +1748,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1682
1748
|
__test: {
|
|
1683
1749
|
css,
|
|
1684
1750
|
applyImproved,
|
|
1751
|
+
closePreview,
|
|
1685
1752
|
closeFeedback,
|
|
1686
1753
|
fadeFeedback,
|
|
1687
1754
|
rootStore,
|
package/docs/README.md
CHANGED
|
@@ -9,13 +9,13 @@ Pick the question you have:
|
|
|
9
9
|
|
|
10
10
|
| I want to… | Read |
|
|
11
11
|
| --- | --- |
|
|
12
|
-
| Install it and see it working in five minutes | [Getting started](getting-started.md) |
|
|
12
|
+
| Install it and see it working in five minutes | [Getting started](getting-started.md) ([中文](getting-started.zh.md)) |
|
|
13
13
|
| Understand what it does, step by step, and what the words mean | [How it works](how-it-works.md) |
|
|
14
14
|
| Know what leaves my machine, what it costs, and what it can't do yet | [Privacy, cost & limitations](privacy-and-cost.md) |
|
|
15
15
|
| Change a setting | [Configuration](configuration.md) |
|
|
16
|
-
| Read or change the code | [Architecture](architecture.md) · [Contributing](../CONTRIBUTING.md) |
|
|
16
|
+
| Read or change the code | [Architecture](architecture.md) · [Contributing](../CONTRIBUTING.md) ([中文](CONTRIBUTING.zh.md)) |
|
|
17
17
|
| See what changed between versions | [Changelog](../CHANGELOG.md) |
|
|
18
18
|
|
|
19
19
|
Reading order for a newcomer: Getting started → How it works → Privacy & cost.
|
|
20
20
|
|
|
21
|
-
中文:[README.zh.md](README.zh.md)
|
|
21
|
+
中文:[README.zh.md](README.zh.md) · [快速开始](getting-started.zh.md) · [贡献指南](CONTRIBUTING.zh.md)(其余文档为英文)。
|
package/docs/README.zh.md
CHANGED
|
@@ -29,10 +29,10 @@ Tacit: 学到 → "用户常常不说明是哪个应用——先查 apps/web。
|
|
|
29
29
|
## 安装——30 秒
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
dsh plugin --profile web add dsh-tacit
|
|
32
|
+
npx @deepseek-ai/dsh plugin --profile web add dsh-tacit
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
用 `npx @deepseek-ai/dsh web` 启动(或重启),然后刷新页面。可选的快速起步:**设置 → Tacit → *从我最近 20 轮中学习***
|
|
36
36
|
(约 $0.02–0.05,一次性)。
|
|
37
37
|
|
|
38
38
|
需要 DeepSeek Harness `>= 0.1.1-rc.1`、Node `>= 22`,以及已在 harness 里配置好的
|
|
@@ -68,15 +68,16 @@ Tacit 从不接触你的 API Key(所有调用都经过 harness 自己的模型
|
|
|
68
68
|
完整的数据流与成本表、以及坦白的限制清单(英文):
|
|
69
69
|
[Privacy, cost & limitations](https://github.com/hackernotfound/dsh-tacit/blob/main/docs/privacy-and-cost.md)。
|
|
70
70
|
|
|
71
|
-
##
|
|
71
|
+
## 文档(除标注外均为英文)
|
|
72
72
|
|
|
73
73
|
| | |
|
|
74
74
|
| --- | --- |
|
|
75
|
-
| [
|
|
75
|
+
| [快速开始](https://github.com/hackernotfound/dsh-tacit/blob/main/docs/getting-started.zh.md)([English](https://github.com/hackernotfound/dsh-tacit/blob/main/docs/getting-started.md)) | 安装、确认已启用、快速起步、界面各处在哪、排障 |
|
|
76
76
|
| [How it works](https://github.com/hackernotfound/dsh-tacit/blob/main/docs/how-it-works.md) | 逐步流程,带图和术语表 |
|
|
77
77
|
| [Privacy, cost & limitations](https://github.com/hackernotfound/dsh-tacit/blob/main/docs/privacy-and-cost.md) | 什么留在本地、什么会发出去、每次调用的花费、目前做不到的事 |
|
|
78
78
|
| [Configuration](https://github.com/hackernotfound/dsh-tacit/blob/main/docs/configuration.md) | 所有设置项、默认值和取值范围 |
|
|
79
79
|
| [Architecture](https://github.com/hackernotfound/dsh-tacit/blob/main/docs/architecture.md) | 面向贡献者:模块、钩子、路由、存储 |
|
|
80
|
-
| [
|
|
80
|
+
| [贡献指南](https://github.com/hackernotfound/dsh-tacit/blob/main/docs/CONTRIBUTING.zh.md)([English](https://github.com/hackernotfound/dsh-tacit/blob/main/CONTRIBUTING.md)) | fork → 分支 → PR 的流程、测试、基本规则;欢迎提 issue 和 PR |
|
|
81
|
+
| [Changelog](https://github.com/hackernotfound/dsh-tacit/blob/main/CHANGELOG.md) | 各版本的变更 |
|
|
81
82
|
|
|
82
83
|
MIT © hackernotfound
|