dsh-tacit 0.2.1 → 0.2.2

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,
package/client/client.js CHANGED
@@ -12,7 +12,7 @@
12
12
  * slot, beside Chat/Trajectory) listing every turn's digest with an
13
13
  * Analyze button and the analysis report;
14
14
  * - a small ✨ Improve button in the composer tool row
15
- * (`conversation.input.left`) once Tacit has learned enough patterns;
15
+ * (`conversation.input.left`) while it is enabled in Settings;
16
16
  * - a before/after preview popup in `conversation.input.overlay` whose
17
17
  * Apply action replaces the composer draft via `inputActions.setDraft`.
18
18
  *
@@ -59,7 +59,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
59
59
  'trigger.bootstrap': '引导',
60
60
  'bootstrap.btn': '从我最近 20 轮中学习',
61
61
  'bootstrap.running': '学习中… {done}/{total}',
62
- 'bootstrap.hint': ' $0.02,一次性。已顺利完成的提示词会被跳过。',
62
+ 'bootstrap.hint': ' $0.02–0.05,一次性。已顺利完成的提示词会被跳过。',
63
63
  'steer.enrich': '发送前补充学到的上下文(实验性,每次发送一次小调用)',
64
64
  'steer.title': '智能体被告知的关于你的信息',
65
65
  'steer.desc': '这些指令会注入每个新会话的系统提示词,让智能体替你补上你通常没说的内容。',
@@ -166,7 +166,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
166
166
  'trigger.bootstrap': 'bootstrap',
167
167
  'bootstrap.btn': 'Learn from my last 20 turns',
168
168
  'bootstrap.running': 'Learning… {done}/{total}',
169
- 'bootstrap.hint': '≈ $0.02, one time. Skips prompts that already went fine on their own.',
169
+ 'bootstrap.hint': '≈ $0.02–0.05, one time. Skips prompts that already went fine on their own.',
170
170
  'steer.enrich': 'Add learned context before each send (experimental — one small call per send)',
171
171
  'steer.title': 'What the agent is told about you',
172
172
  'steer.desc': 'These directives ride every new session\'s system prompt so the agent fills in what you usually leave unsaid.',
@@ -1220,8 +1220,9 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
1220
1220
  function PreviewOverlay(kit) {
1221
1221
  const { t } = kit
1222
1222
  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
1223
+ const store = props.tacitStore !== null && typeof props.tacitStore === 'object'
1224
+ ? props.tacitStore
1225
+ : null
1225
1226
  useStoreVersion(store)
1226
1227
  if (store === null || !store.preview.open) return null
1227
1228
 
@@ -1258,7 +1259,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
1258
1259
  }
1259
1260
  }
1260
1261
 
1261
- // ── Feedback strip (under the composer, live stage only) ────────────────
1262
+ // ── Feedback strip (under the composer, Improve enabled only) ───────────
1262
1263
 
1263
1264
  function FeedbackStrip(kit) {
1264
1265
  const { t } = kit
@@ -1646,11 +1647,11 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
1646
1647
  id: 'tacit-preview',
1647
1648
  order: 10,
1648
1649
  locale: NS,
1649
- inject: (sessionId) => ({ hooks: { tacitStore: storeFor(sessionId) } }),
1650
+ inject: (sessionId) => ({ tacitStore: storeFor(sessionId) }),
1650
1651
  }, (props) => h(PreviewOverlayView, props)))
1651
1652
 
1652
1653
  // The post-apply 👍/👎 strip lives in the band UNDER the composer card
1653
- // (composer.dock), only for the live stage (threshold reached).
1654
+ // (composer.dock), only while the Improve button is enabled.
1654
1655
  const FeedbackStripView = FeedbackStrip(kit)
1655
1656
  ctx.slots.inject('conversation.composer.dock', () => ctx.slots.register({
1656
1657
  name: 'conversation.composer.dock',
@@ -1661,8 +1662,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
1661
1662
  }, (props) => h(FeedbackStripView, props)))
1662
1663
 
1663
1664
  // 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
1665
+ // on its own. Reuses the coach panel. Order 32 places it right after
1666
1666
  // cost-meter's "Cost" section (order 30/31).
1667
1667
  const SettingsSectionView = SettingsSection(kit)
1668
1668
  ctx.slots.inject('settings.section', () => ctx.slots.register({
@@ -1682,6 +1682,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
1682
1682
  __test: {
1683
1683
  css,
1684
1684
  applyImproved,
1685
+ closePreview,
1685
1686
  closeFeedback,
1686
1687
  fadeFeedback,
1687
1688
  rootStore,
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 里配置好的
package/lib/analyze.js CHANGED
@@ -314,8 +314,8 @@ export const IMPROVE_REPAIR_SYSTEM_PROMPT = [
314
314
  ].join('\n')
315
315
 
316
316
  /**
317
- * Style-rule distillation: the ONLY new paid model call of the v2 loop,
318
- * fired rarely (every 3+ unreviewed 👎 reasons) with a hard 300-token cap.
317
+ * Style-rule distillation: fired rarely (every 3+ unreviewed 👎 reasons),
318
+ * capped at DISTILL_MAX_TOKENS output tokens.
319
319
  */
320
320
  export const DISTILL_SYSTEM_PROMPT = [
321
321
  'You distill user feedback into durable prompt-writing style rules for a',
package/lib/service.js CHANGED
@@ -8,8 +8,9 @@
8
8
  * an `ok` flag and a stable error `code` the client localizes. Expected
9
9
  * failures never throw; unexpected ones are mapped by the route layer.
10
10
  *
11
- * Only user-triggered model calls exist here (analyzeTurn / improveDraft):
12
- * there is no background work, no polling, no telemetry.
11
+ * Model calls are either user-triggered (analyzeTurn / improveDraft /
12
+ * bootstrap) or automatic analyses of messy and corrected turns, the latter
13
+ * capped by `autoDailyBudget`. There is no polling and no telemetry.
13
14
  */
14
15
 
15
16
  import {
@@ -285,7 +286,7 @@ export function createCoachService(ctx, store, effectiveConfig) {
285
286
  * Returns the profile with fresh rules on success, the input profile on a
286
287
  * soft no-op/failure.
287
288
  */
288
- const maybeDistill = async (profile, provider) => {
289
+ const maybeDistill = async (profile, provider, sessionId) => {
289
290
  if (profile.pendingDistill < 3 || distillInFlight) return profile
290
291
  distillInFlight = true
291
292
  try {
@@ -299,6 +300,7 @@ export function createCoachService(ctx, store, effectiveConfig) {
299
300
  maxTokens: DISTILL_MAX_TOKENS,
300
301
  timeoutMs: DISTILL_TIMEOUT_MS,
301
302
  tool: DISTILL_TOOL,
303
+ sessionId,
302
304
  })
303
305
  const rules = normalizeDistillRules(text)
304
306
  if (rules.length === 0) return profile
@@ -843,7 +845,7 @@ export function createCoachService(ctx, store, effectiveConfig) {
843
845
  // Distillation also fires on user-triggered improve calls (soft, in-flight
844
846
  // deduped, never awaited: an improve call is never blocked by it).
845
847
  if (profile.pendingDistill >= 3) {
846
- maybeDistill(profile, providerForSession(sessionId)).catch(() => {})
848
+ maybeDistill(profile, providerForSession(sessionId), sessionId).catch(() => {})
847
849
  }
848
850
  // Only trusted (or still-inexperienced) patterns reach the prompt;
849
851
  // style rules + the last 3 verbatim down-reasons ride along for free
@@ -948,7 +950,7 @@ export function createCoachService(ctx, store, effectiveConfig) {
948
950
  // user action. Awaited so the returned profile already carries the
949
951
  // fresh style rules; a failure is soft-silent and retries later.
950
952
  if (profile.pendingDistill >= 3) {
951
- profile = await maybeDistill(profile, providerForSession(record.sessionId))
953
+ profile = await maybeDistill(profile, providerForSession(record.sessionId), record.sessionId)
952
954
  }
953
955
  return { ok: true, profile, code: '', detail: '' }
954
956
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-tacit",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Tacit learns what you leave unsaid in your prompts — from messy turns and your own corrections, with zero clicks — and tells the agent how to compensate, on every turn, via a system-prompt section you can read and edit.",
5
5
  "author": "hackernotfound",
6
6
  "license": "MIT",
@@ -91,8 +91,8 @@
91
91
  "zod": "^4.4.3"
92
92
  },
93
93
  "devDependencies": {
94
- "react": "^18.3.1",
95
- "react-dom": "^18.3.1"
94
+ "react": "^19.2.8",
95
+ "react-dom": "^19.2.8"
96
96
  },
97
97
  "keywords": [
98
98
  "deepseek",