dsh-tacit 0.3.0 → 0.5.0
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 +7 -5
- package/client/client.js +367 -74
- package/docs/README.zh.md +2 -1
- package/lib/analyze.js +215 -24
- package/lib/fold.js +11 -5
- package/lib/index.js +1 -1
- package/lib/redact.js +53 -0
- package/lib/routes.js +1 -0
- package/lib/schema.js +98 -25
- package/lib/service.js +282 -134
- package/lib/store.js +66 -5
- package/lib/usage.js +139 -41
- package/lib/workspace.js +62 -0
- package/package.json +1 -1
package/lib/schema.js
CHANGED
|
@@ -29,7 +29,7 @@ export const COACH_PROVIDER = 'deepseek-official'
|
|
|
29
29
|
* outside this list onto one of these. Every entry has an `err.*` key in both
|
|
30
30
|
* dictionaries (`client/src/10-i18n.js`, test-enforced).
|
|
31
31
|
*/
|
|
32
|
-
export const COACH_ERROR_CODES = ['no-llm', 'no-api-key', 'rate-limited', 'timeout', 'empty-response', 'call-failed']
|
|
32
|
+
export const COACH_ERROR_CODES = ['no-llm', 'no-api-key', 'no-credit', 'rate-limited', 'timeout', 'empty-response', 'call-failed']
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* The loader-facing plugin config. Wrapped in `z.preprocess` so a patch row
|
|
@@ -79,6 +79,8 @@ export const Config = z.preprocess((v) => v ?? {}, z.object({
|
|
|
79
79
|
costWarnDailyUsd: z.number().default(0),
|
|
80
80
|
/** Same as `costWarnDailyUsd`, over a calendar month; 0 disables (clamped in mergeConfig). */
|
|
81
81
|
costWarnMonthlyUsd: z.number().default(0),
|
|
82
|
+
/** Opt-in: a distilled directive stays queued until *Start trial* is pressed on it. */
|
|
83
|
+
reviewCandidates: z.boolean().default(false),
|
|
82
84
|
}))
|
|
83
85
|
|
|
84
86
|
/**
|
|
@@ -107,6 +109,7 @@ const configPatchSchema = z.object({
|
|
|
107
109
|
costHistoryDays: z.number().optional(),
|
|
108
110
|
costWarnDailyUsd: z.number().optional(),
|
|
109
111
|
costWarnMonthlyUsd: z.number().optional(),
|
|
112
|
+
reviewCandidates: z.boolean().optional(),
|
|
110
113
|
})
|
|
111
114
|
|
|
112
115
|
// ── Trajectory projection ──────────────────────────────────────────────────
|
|
@@ -232,29 +235,77 @@ const feedbackEntrySchema = z.object({
|
|
|
232
235
|
* system prompt). `distilled` entries come from analyses; `user` entries are
|
|
233
236
|
* typed in Settings and survive every distillation.
|
|
234
237
|
*/
|
|
235
|
-
const directiveTrialSchema = z.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
238
|
+
const directiveTrialSchema = z.preprocess(
|
|
239
|
+
// Profiles written before corrections were graded named the messy baseline `baselineRate`.
|
|
240
|
+
(raw) => (raw !== null && typeof raw === 'object' && 'baselineRate' in raw && !('baselineMessyRate' in raw) ? { ...raw, baselineMessyRate: raw.baselineRate } : raw),
|
|
241
|
+
z.object({
|
|
242
|
+
/** Finished turns observed while the candidate was injected. */
|
|
243
|
+
turns: z.number().int().min(0),
|
|
244
|
+
/** How many of those were messy. */
|
|
245
|
+
messy: z.number().int().min(0),
|
|
246
|
+
/** How many of those the user corrected with their next message. */
|
|
247
|
+
corrected: z.number().int().min(0).default(0),
|
|
248
|
+
/** Messy-turn rate over the 20 turns before the trial started. */
|
|
249
|
+
baselineMessyRate: z.number(),
|
|
250
|
+
/** Correction rate over those 20 turns; -1 = unknown (older profile), recomputed when the trial next advances. */
|
|
251
|
+
baselineCorrectionRate: z.number().default(-1),
|
|
252
|
+
startedAt: z.number(),
|
|
253
|
+
}),
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
/** One analysis report a distillation read: ids only, never the prompt. */
|
|
257
|
+
const directiveEvidenceSchema = z.object({
|
|
258
|
+
sessionId: z.string(),
|
|
259
|
+
turn: z.number(),
|
|
260
|
+
trigger: z.string(),
|
|
243
261
|
})
|
|
244
262
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
263
|
+
/**
|
|
264
|
+
* queued = waiting for its scope's trial slot (or, under `reviewCandidates`, for the user's *Start trial*);
|
|
265
|
+
* candidate = injected on trial; active = proven (or user-made); retired = made things worse;
|
|
266
|
+
* removed = deleted by the user. Retired and removed records are kept so the distiller is told
|
|
267
|
+
* not to propose them again.
|
|
268
|
+
*/
|
|
269
|
+
export const DIRECTIVE_STATUSES = ['queued', 'candidate', 'active', 'retired', 'removed']
|
|
270
|
+
|
|
271
|
+
const directiveSchema = z.preprocess(
|
|
272
|
+
(raw) => {
|
|
273
|
+
if (raw === null || typeof raw !== 'object') return raw
|
|
274
|
+
const entry = { ...raw }
|
|
275
|
+
// Profiles written before provenance was recorded: the text is as old as the record.
|
|
276
|
+
if (typeof entry.updatedAt !== 'number') entry.updatedAt = entry.createdAt
|
|
277
|
+
// A candidate is always enabled: switched off, it leaves the trial slot and queues again.
|
|
278
|
+
if (entry.status === 'candidate' && entry.enabled === false) {
|
|
279
|
+
entry.status = 'queued'
|
|
280
|
+
delete entry.trial
|
|
281
|
+
}
|
|
282
|
+
return entry
|
|
283
|
+
},
|
|
284
|
+
z.object({
|
|
285
|
+
id: z.string(),
|
|
286
|
+
text: z.string(),
|
|
287
|
+
enabled: z.boolean().default(true),
|
|
288
|
+
source: z.enum(['distilled', 'user']).default('distilled'),
|
|
289
|
+
createdAt: z.number(),
|
|
290
|
+
/** Last time the text changed (a reword) or the record moved state. */
|
|
291
|
+
updatedAt: z.number(),
|
|
292
|
+
/** Bumped on every reword of the text. */
|
|
293
|
+
version: z.number().int().min(1).default(1),
|
|
294
|
+
status: z.enum(DIRECTIVE_STATUSES).default('active'),
|
|
295
|
+
trial: directiveTrialSchema.optional(),
|
|
296
|
+
retiredReason: z.string().optional(),
|
|
297
|
+
/** Absolute workspace directory this directive is limited to; absent = every conversation. */
|
|
298
|
+
workspace: z.string().optional(),
|
|
299
|
+
/** The reports the distillation that wrote this text read (newest first, capped). */
|
|
300
|
+
evidence: z.array(directiveEvidenceSchema).default([]),
|
|
301
|
+
/** The usage-ledger run of that distillation, so its cost is one lookup away. '' for user-typed. */
|
|
302
|
+
distillationRunId: z.string().default(''),
|
|
303
|
+
/** When the trial reached its verdict; 0 = never. */
|
|
304
|
+
evaluatedAt: z.number().default(0),
|
|
305
|
+
/** When the user pressed *Start trial* under `reviewCandidates`; 0 = not yet. */
|
|
306
|
+
approvedAt: z.number().default(0),
|
|
307
|
+
}),
|
|
308
|
+
)
|
|
258
309
|
|
|
259
310
|
/** The persistent user-wide mistake profile. */
|
|
260
311
|
export const profileSchema = z.object({
|
|
@@ -277,6 +328,8 @@ export const profileSchema = z.object({
|
|
|
277
328
|
directives: z.array(directiveSchema).default([]),
|
|
278
329
|
/** New analyses since the last directive distillation. */
|
|
279
330
|
analysesSinceDirectives: z.number().int().min(0).default(0),
|
|
331
|
+
/** When a session was last loaded in each directive's workspace, so the least recently seen one is dropped first. */
|
|
332
|
+
workspaceSeenAt: z.record(z.string(), z.number()).default({}),
|
|
280
333
|
})
|
|
281
334
|
|
|
282
335
|
// ── Route argument codecs ──────────────────────────────────────────────────
|
|
@@ -293,9 +346,16 @@ export const statsArgSchema = z.object({
|
|
|
293
346
|
export const directivesArgSchema = z.discriminatedUnion('action', [
|
|
294
347
|
z.object({ action: z.literal('toggle'), id: z.string().min(1).max(64), enabled: z.boolean() }),
|
|
295
348
|
z.object({ action: z.literal('add'), text: z.string().min(1).max(300), workspace: z.string().max(1000).optional() }),
|
|
349
|
+
z.object({ action: z.literal('rescope'), id: z.string().min(1).max(64), workspace: z.string().max(1000) }),
|
|
296
350
|
z.object({ action: z.literal('remove'), id: z.string().min(1).max(64) }),
|
|
351
|
+
z.object({ action: z.literal('start-trial'), id: z.string().min(1).max(64) }),
|
|
297
352
|
])
|
|
298
353
|
|
|
354
|
+
/** `/api/tacit/directive-receipt`: one directive's provenance and cost, by id. */
|
|
355
|
+
export const directiveReceiptArgSchema = z.object({
|
|
356
|
+
id: z.string().min(1).max(64),
|
|
357
|
+
})
|
|
358
|
+
|
|
299
359
|
// ── Route argument codecs ──────────────────────────────────────────────────
|
|
300
360
|
|
|
301
361
|
export const sessionArgSchema = z.object({
|
|
@@ -305,12 +365,15 @@ export const sessionArgSchema = z.object({
|
|
|
305
365
|
export const analyzeArgSchema = z.object({
|
|
306
366
|
sessionId: z.string().min(1).max(200),
|
|
307
367
|
turn: z.number().int().min(1),
|
|
368
|
+
/** Re-analyze a turn that already has a report (otherwise `already-analyzed`, at no cost). */
|
|
369
|
+
force: z.boolean().optional(),
|
|
308
370
|
})
|
|
309
371
|
|
|
310
372
|
/** `/api/tacit/analyze-batch`: one session, up to 50 turns analyzed under a single run. */
|
|
311
373
|
export const analyzeBatchArgSchema = z.object({
|
|
312
374
|
sessionId: z.string().min(1).max(200),
|
|
313
375
|
turns: z.array(z.number().int().min(1)).min(1).max(50),
|
|
376
|
+
force: z.boolean().optional(),
|
|
314
377
|
})
|
|
315
378
|
|
|
316
379
|
export const improveArgSchema = z.object({
|
|
@@ -347,6 +410,12 @@ export const USAGE_OPS = [
|
|
|
347
410
|
'enrichment',
|
|
348
411
|
]
|
|
349
412
|
|
|
413
|
+
/** Every state one recorded attempt can end in. */
|
|
414
|
+
export const USAGE_ATTEMPT_STATUSES = ['ok', 'failed', 'unmetered']
|
|
415
|
+
|
|
416
|
+
/** Every state a run can be in. `running` is written too: the flush persists live runs. */
|
|
417
|
+
export const USAGE_RUN_STATUSES = ['running', 'success', 'partial', 'failed']
|
|
418
|
+
|
|
350
419
|
/** Every kind of run the tracker groups attempts into. */
|
|
351
420
|
export const USAGE_RUN_TYPES = [
|
|
352
421
|
'bootstrap',
|
|
@@ -382,7 +451,7 @@ export const usageAttemptSchema = z.object({
|
|
|
382
451
|
provider: z.string().default(''),
|
|
383
452
|
reasoningEffort: z.string().nullable().default(null),
|
|
384
453
|
finish: z.string().default(''),
|
|
385
|
-
status: z.enum(
|
|
454
|
+
status: z.enum(USAGE_ATTEMPT_STATUSES),
|
|
386
455
|
code: z.string().default(''),
|
|
387
456
|
sessionId: z.string().default(''),
|
|
388
457
|
turn: z.number().nullable().default(null),
|
|
@@ -410,10 +479,12 @@ const emptyTokenBuckets = tokenBucketsSchema.parse({})
|
|
|
410
479
|
export const usageTotalsSchema = z.object({
|
|
411
480
|
attempts: z.number().default(0),
|
|
412
481
|
billedCalls: z.number().default(0),
|
|
482
|
+
failedCalls: z.number().default(0),
|
|
413
483
|
unmeteredCalls: z.number().default(0),
|
|
414
484
|
unpricedCalls: z.number().default(0),
|
|
415
485
|
tokens: tokenBucketsSchema.default(emptyTokenBuckets),
|
|
416
486
|
usdKnown: z.number().default(0),
|
|
487
|
+
failedUsd: z.number().default(0),
|
|
417
488
|
})
|
|
418
489
|
|
|
419
490
|
const emptyUsageTotals = usageTotalsSchema.parse({})
|
|
@@ -425,7 +496,7 @@ export const usageRunSchema = z.object({
|
|
|
425
496
|
trigger: z.string().default(''),
|
|
426
497
|
startedAt: z.number(),
|
|
427
498
|
endedAt: z.number().default(0),
|
|
428
|
-
status: z.enum(
|
|
499
|
+
status: z.enum(USAGE_RUN_STATUSES).default('running'),
|
|
429
500
|
sessionId: z.string().default(''),
|
|
430
501
|
turn: z.number().nullable().default(null),
|
|
431
502
|
workspace: z.string().default(''),
|
|
@@ -454,6 +525,8 @@ export const usageSummarySchema = z.object({
|
|
|
454
525
|
lifetime: usageTotalsSchema.default(emptyUsageTotals),
|
|
455
526
|
byType: z.record(usageTotalsSchema).default({}),
|
|
456
527
|
byModel: z.record(usageTotalsSchema).default({}),
|
|
528
|
+
byProvider: z.record(usageTotalsSchema).default({}),
|
|
529
|
+
byTrigger: z.record(usageTotalsSchema).default({}),
|
|
457
530
|
days: z.record(usageDayTotalsSchema).default({}),
|
|
458
531
|
})
|
|
459
532
|
|
|
@@ -466,7 +539,7 @@ export const usageSummarySchema = z.object({
|
|
|
466
539
|
export const usageArgSchema = z.object({
|
|
467
540
|
range: z.enum(['today', '7d', '30d', 'month', 'all']).optional(),
|
|
468
541
|
type: z.enum(USAGE_RUN_TYPES).optional(),
|
|
469
|
-
status: z.enum(
|
|
542
|
+
status: z.enum(USAGE_RUN_STATUSES).optional(),
|
|
470
543
|
model: z.string().max(64).optional(),
|
|
471
544
|
workspace: z.string().max(200).optional(),
|
|
472
545
|
sessionId: z.string().max(200).optional(),
|