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 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
- Restart `dsh web`, refresh the page. Optional kick-start: **Settings Tacit
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`) once Tacit has learned enough patterns;
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': ' $0.02,一次性。已顺利完成的提示词会被跳过。',
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 (sidebar action + settings section + frame overlay) ─────
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.problems.length === 0
951
- ? h('div', { className: 'tacit-report-note' }, '—')
952
- : report.problems.map((problem, index) => ProblemRow(kit, problem, index)),
953
- h('div', { className: 'tacit-report-title' }, t('report.improved')),
954
- improved.length > 0
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 hooks = props.hooks !== null && typeof props.hooks === 'object' ? props.hooks : null
1224
- const store = hooks !== null ? hooks.tacitStore : null
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 (under the composer, live stage only) ────────────────
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 (settings section, sidebar action, frame overlay) ─────
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, { config, directives, steering: rootStore.steering }),
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:8px 2px 0;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}'
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) => ({ hooks: { tacitStore: storeFor(sessionId) } }),
1715
+ inject: (sessionId) => ({ tacitStore: storeFor(sessionId) }),
1650
1716
  }, (props) => h(PreviewOverlayView, props)))
1651
1717
 
1652
- // The post-apply 👍/👎 strip lives in the band UNDER the composer card
1653
- // (composer.dock), only for the live stage (threshold reached).
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.composer.dock', () => ctx.slots.register({
1656
- name: 'conversation.composer.dock',
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 (progress, model, threshold, live,
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)(仅 README 有中文版)。
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
- 重启 `dsh web`,刷新页面。可选的快速起步:**设置 → Tacit → *从我最近 20 轮中学习***
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
- | [Getting started](https://github.com/hackernotfound/dsh-tacit/blob/main/docs/getting-started.md) | 安装、确认已启用、快速起步、界面各处在哪、排障 |
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
- | [Contributing](https://github.com/hackernotfound/dsh-tacit/blob/main/CONTRIBUTING.md) · [Changelog](https://github.com/hackernotfound/dsh-tacit/blob/main/CHANGELOG.md) | |
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