dsh-tacit 0.6.0 → 0.6.1
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/analyze.js +42 -12
- package/lib/service.js +2 -0
- package/lib/usage.js +6 -4
- package/package.json +3 -2
package/lib/analyze.js
CHANGED
|
@@ -210,6 +210,28 @@ export function capDirectives(list, { seenAt = {} } = {}) {
|
|
|
210
210
|
return capScopes(capped, seenAt)
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
/**
|
|
214
|
+
* One candidate per scope: the earliest-started trial (then earliest created,
|
|
215
|
+
* then first listed) keeps the slot, every other candidate in that scope goes
|
|
216
|
+
* back to the queue and drops its trial. Idempotent; entries mutate in place.
|
|
217
|
+
*/
|
|
218
|
+
export function settleTrialSlots(list) {
|
|
219
|
+
const startOf = (entry) => (typeof entry.trial?.startedAt === 'number' ? entry.trial.startedAt : typeof entry.createdAt === 'number' ? entry.createdAt : Infinity)
|
|
220
|
+
const kept = new Map()
|
|
221
|
+
for (const entry of list) {
|
|
222
|
+
if (entry.status !== 'candidate') continue
|
|
223
|
+
const scope = scopeOf(entry)
|
|
224
|
+
const holder = kept.get(scope)
|
|
225
|
+
if (holder === undefined || startOf(entry) < startOf(holder)) kept.set(scope, entry)
|
|
226
|
+
}
|
|
227
|
+
for (const entry of list) {
|
|
228
|
+
if (entry.status !== 'candidate' || kept.get(scopeOf(entry)) === entry) continue
|
|
229
|
+
entry.status = 'queued'
|
|
230
|
+
delete entry.trial
|
|
231
|
+
}
|
|
232
|
+
return list
|
|
233
|
+
}
|
|
234
|
+
|
|
213
235
|
/**
|
|
214
236
|
* Merge the model's new complete set of directives ({ id?, text, workspace? })
|
|
215
237
|
* into the profile. Three groups, and only the third is replaceable:
|
|
@@ -1092,19 +1114,27 @@ export function aggregateProfile(prev, report, maxPatterns, options = {}) {
|
|
|
1092
1114
|
const countNew = options.countNew !== false
|
|
1093
1115
|
const patterns = new Map()
|
|
1094
1116
|
for (const pattern of Array.isArray(prev?.patterns) ? prev.patterns : []) {
|
|
1095
|
-
if (pattern
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1117
|
+
if (pattern === null || typeof pattern !== 'object' || typeof pattern.kind !== 'string') continue
|
|
1118
|
+
const kind = normalizeKind(pattern.kind)
|
|
1119
|
+
const next = {
|
|
1120
|
+
kind,
|
|
1121
|
+
count: typeof pattern.count === 'number' && pattern.count > 0 ? pattern.count : 0,
|
|
1122
|
+
lastExample: typeof pattern.lastExample === 'string' ? pattern.lastExample : '',
|
|
1123
|
+
applied: counterOf(pattern, 'applied'),
|
|
1124
|
+
accepted: counterOf(pattern, 'accepted'),
|
|
1125
|
+
rejected: counterOf(pattern, 'rejected'),
|
|
1126
|
+
verified: counterOf(pattern, 'verified'),
|
|
1127
|
+
unverified: counterOf(pattern, 'unverified'),
|
|
1128
|
+
resolved: counterOf(pattern, 'resolved'),
|
|
1129
|
+
}
|
|
1130
|
+
const current = patterns.get(kind)
|
|
1131
|
+
if (current === undefined) {
|
|
1132
|
+
patterns.set(kind, next)
|
|
1133
|
+
continue
|
|
1107
1134
|
}
|
|
1135
|
+
// Stored spellings that normalise to one kind ('missing context' / 'missing-context') fold into one row.
|
|
1136
|
+
if (next.count > current.count) current.lastExample = next.lastExample
|
|
1137
|
+
for (const field of ['count', 'applied', 'accepted', 'rejected', 'verified', 'unverified', 'resolved']) current[field] += next[field]
|
|
1108
1138
|
}
|
|
1109
1139
|
// A good-prompt report says which habits the user overcame on their own this time.
|
|
1110
1140
|
for (const strength of Array.isArray(report?.strengths) ? report.strengths : []) {
|
package/lib/service.js
CHANGED
|
@@ -89,6 +89,7 @@ import {
|
|
|
89
89
|
renderSteeringSection,
|
|
90
90
|
scopeOf,
|
|
91
91
|
capDirectives,
|
|
92
|
+
settleTrialSlots,
|
|
92
93
|
mergeDirectives,
|
|
93
94
|
isDeadDirective,
|
|
94
95
|
MAX_DIRECTIVE_EVIDENCE,
|
|
@@ -328,6 +329,7 @@ export function createCoachService(ctx, store, effectiveConfig) {
|
|
|
328
329
|
if (workspace.length > 0) entry.workspace = workspace
|
|
329
330
|
else delete entry.workspace
|
|
330
331
|
}
|
|
332
|
+
settleTrialSlots(parsed.data.directives)
|
|
331
333
|
return parsed.data
|
|
332
334
|
}
|
|
333
335
|
let directiveSeq = 0
|
package/lib/usage.js
CHANGED
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* Two hard rules shape the design:
|
|
12
12
|
* - **Synchronous.** Every mutation here is plain JS with no `await`, so the
|
|
13
13
|
* four bootstrap workers calling sinks concurrently interleave safely and
|
|
14
|
-
* no model call ever waits on the ledger.
|
|
15
|
-
* and
|
|
16
|
-
*
|
|
14
|
+
* no model call ever waits on the ledger. `endRun` writes the run's day
|
|
15
|
+
* file and the summary before it returns, so a host that exits right after
|
|
16
|
+
* a run loses nothing; attempts of still-live runs reach disk through a
|
|
17
|
+
* debounced, `unref()`'d flush timer that never holds the process open.
|
|
17
18
|
* - **Content-free.** A run carries ids, counts, tokens and money — never
|
|
18
19
|
* prompts, responses, tool arguments or full paths (`workspace` is the
|
|
19
20
|
* label, not the path).
|
|
@@ -388,7 +389,7 @@ export function createUsageTracker({ store, config, pricing, now = Date.now, flu
|
|
|
388
389
|
return (record) => recordAttempt(runId, tag, record)
|
|
389
390
|
}
|
|
390
391
|
|
|
391
|
-
/** Close a run: derive its status, write its day file, evict it, and expire old days. */
|
|
392
|
+
/** Close a run: derive its status, write its day file and the summary, evict it, and expire old days. */
|
|
392
393
|
function endRun(runId, { results = {}, status } = {}) {
|
|
393
394
|
const run = live.get(runId)
|
|
394
395
|
if (run === undefined) return null
|
|
@@ -399,6 +400,7 @@ export function createUsageTracker({ store, config, pricing, now = Date.now, flu
|
|
|
399
400
|
live.delete(runId)
|
|
400
401
|
remember(run)
|
|
401
402
|
pruneIfNewDay()
|
|
403
|
+
flush()
|
|
402
404
|
return runSummary(runId)
|
|
403
405
|
}
|
|
404
406
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-tacit",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
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",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"check:client": "node scripts/build-client.mjs --check",
|
|
43
43
|
"check:docs": "node scripts/check-doc-links.mjs",
|
|
44
44
|
"check:package": "node scripts/check-package.mjs",
|
|
45
|
-
"smoke": "node scripts/smoke.mjs"
|
|
45
|
+
"smoke": "node scripts/smoke.mjs",
|
|
46
|
+
"rehearse": "node scripts/rehearse.mjs"
|
|
46
47
|
},
|
|
47
48
|
"publishConfig": {
|
|
48
49
|
"access": "public"
|