wendkeep 0.66.4 → 0.67.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.en.md +78 -5
  3. package/README.md +78 -5
  4. package/docs/en/commands/costs-and-observability.md +21 -7
  5. package/docs/en/commands/maintenance-and-diagnostics.md +13 -1
  6. package/docs/en/commands/operating-profiles.md +65 -10
  7. package/docs/en/commands/sessions-and-import.md +22 -1
  8. package/docs/en/commands/verify.md +5 -3
  9. package/docs/pt-BR/commands/costs-and-observability.md +21 -7
  10. package/docs/pt-BR/commands/maintenance-and-diagnostics.md +12 -1
  11. package/docs/pt-BR/commands/operating-profiles.md +66 -11
  12. package/docs/pt-BR/commands/sessions-and-import.md +20 -0
  13. package/docs/pt-BR/commands/verify.md +6 -3
  14. package/hooks/change-nag.mjs +8 -0
  15. package/hooks/codex-rollout-meta.mjs +112 -0
  16. package/hooks/codex-subagent-graph.mjs +903 -0
  17. package/hooks/harness-doctor.mjs +82 -1
  18. package/hooks/import-sessions.mjs +185 -50
  19. package/hooks/operating-profile-runtime.mjs +36 -2
  20. package/hooks/operating-profile-task-store.mjs +77 -0
  21. package/hooks/session-identity.mjs +40 -5
  22. package/hooks/session-observability-lifecycle.mjs +129 -0
  23. package/hooks/session-observability-state.mjs +241 -0
  24. package/hooks/session-observability-store.mjs +436 -0
  25. package/hooks/session-observability.mjs +647 -21
  26. package/hooks/session-stop.mjs +339 -11
  27. package/hooks/subagent-stop.mjs +266 -12
  28. package/hooks/subagent-usage.mjs +65 -0
  29. package/hooks/token-usage.mjs +81 -4
  30. package/package.json +3 -3
  31. package/packages/harness/src/operating-profile.mjs +127 -0
  32. package/packages/harness/src/sensors-core.mjs +41 -1
  33. package/packages/integrations/src/prompt-content.mjs +123 -0
  34. package/packages/integrations/src/transcripts.mjs +16 -10
  35. package/src/cost.mjs +40 -6
  36. package/src/doctor.mjs +4 -1
  37. package/src/profile.mjs +95 -17
  38. package/src/rebuild-costs.mjs +220 -34
  39. package/src/skills-seed.mjs +38 -2
  40. package/src/sync-defs.mjs +6 -1
@@ -0,0 +1,436 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import {
5
+ VAULT_LOCK_BUSY,
6
+ assertVaultPathSafe,
7
+ mkdirVaultPath,
8
+ withVaultPathLock,
9
+ writeVaultFileAtomic,
10
+ } from './vault-path-safety.mjs';
11
+ import { sanitizeObservabilityDiagnostics } from './session-observability-state.mjs';
12
+
13
+ const STORE_SCHEMA_VERSION = 1;
14
+ const STORE_DIR_PARTS = ['.brain', 'runtime', 'session-observability'];
15
+ const STORE_PATH_CODE = 'OBSERVABILITY_STORE_PATH_UNSAFE';
16
+ const DEFAULT_SIGNAL_LIMIT = 4_096;
17
+ const SIGNAL_KINDS = new Set(['started', 'interacted', 'interrupted']);
18
+
19
+ function storeError(code, message) {
20
+ const error = new Error(message);
21
+ error.code = code;
22
+ return error;
23
+ }
24
+
25
+ function nonEmptyString(value, code = 'OBSERVABILITY_STORE_INVALID') {
26
+ if (typeof value !== 'string' || !value.trim()) {
27
+ throw storeError(code, 'identificador de observabilidade inválido');
28
+ }
29
+ return value.trim();
30
+ }
31
+
32
+ function sequence(value, fallback = null) {
33
+ if (typeof value === 'string' && /^\d+$/.test(value.trim())) value = Number(value);
34
+ return Number.isSafeInteger(value) && value >= 0 ? value : fallback;
35
+ }
36
+
37
+ function timeValue(value, fallback) {
38
+ return Number.isFinite(value) && value >= 0 ? value : fallback;
39
+ }
40
+
41
+ function jsonClone(value) {
42
+ if (value === undefined) return null;
43
+ try { return JSON.parse(JSON.stringify(value)); }
44
+ catch { throw storeError('OBSERVABILITY_STORE_INVALID', 'estado de observabilidade inválido'); }
45
+ }
46
+
47
+ function defaultState(sessionId, {
48
+ reconstructed = false,
49
+ dirty = false,
50
+ diagnostics = [],
51
+ } = {}) {
52
+ return {
53
+ schema_version: STORE_SCHEMA_VERSION,
54
+ session_id: sessionId,
55
+ observability_signal_sequence: 0,
56
+ observability_checkpoint_sequence: 0,
57
+ observability_dirty: dirty,
58
+ signals: [],
59
+ lease: null,
60
+ checkpoint_frontier: null,
61
+ source_manifest: null,
62
+ graph_cache: null,
63
+ diagnostics: sanitizeObservabilityDiagnostics(diagnostics),
64
+ reconstructed,
65
+ };
66
+ }
67
+
68
+ function normalizeSignal(input) {
69
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
70
+ throw storeError('OBSERVABILITY_SIGNAL_INVALID', 'sinal de observabilidade inválido');
71
+ }
72
+ const rolloutId = nonEmptyString(
73
+ input.rollout_id ?? input.rolloutId ?? input.agent_thread_id ?? input.agentThreadId,
74
+ 'OBSERVABILITY_SIGNAL_INVALID',
75
+ );
76
+ const signal = { rollout_id: rolloutId };
77
+ const transcriptPath = input.transcript_path ?? input.transcriptPath;
78
+ if (typeof transcriptPath === 'string' && transcriptPath.trim()) {
79
+ signal.transcript_path = transcriptPath.trim();
80
+ }
81
+ const parentThreadId = input.parent_thread_id
82
+ ?? input.parentThreadId
83
+ ?? input.parent_rollout_id
84
+ ?? input.parentRolloutId;
85
+ if (typeof parentThreadId === 'string' && parentThreadId.trim()) {
86
+ signal.parent_thread_id = parentThreadId.trim();
87
+ }
88
+ const rawKind = input.kind ?? input.event_kind ?? input.eventKind ?? 'started';
89
+ if (typeof rawKind !== 'string' || !SIGNAL_KINDS.has(rawKind.trim().toLowerCase())) {
90
+ throw storeError('OBSERVABILITY_SIGNAL_INVALID', 'sinal de observabilidade inválido');
91
+ }
92
+ signal.kind = rawKind.trim().toLowerCase();
93
+ const timestamp = input.timestamp ?? input.started_at ?? input.startedAt;
94
+ if (typeof timestamp === 'string' && timestamp.trim()) {
95
+ signal.timestamp = timestamp.trim();
96
+ }
97
+ const agentPath = input.agent_path ?? input.agentPath;
98
+ if (typeof agentPath === 'string' && agentPath.trim()) {
99
+ signal.agent_path = agentPath.trim();
100
+ }
101
+ const activationId = input.activation_id ?? input.activationId;
102
+ if (typeof activationId === 'string' && activationId.trim()) {
103
+ signal.activation_id = activationId.trim();
104
+ }
105
+ for (const [target, source] of [
106
+ ['activation_epoch', input.activation_epoch ?? input.activationEpoch],
107
+ ['turn_sequence', input.turn_sequence ?? input.turnSequence],
108
+ ['signal_sequence', input.signal_sequence ?? input.signalSequence],
109
+ ]) {
110
+ if (source !== undefined) {
111
+ const normalized = sequence(source);
112
+ if (normalized === null) {
113
+ throw storeError('OBSERVABILITY_SIGNAL_INVALID', 'sinal de observabilidade inválido');
114
+ }
115
+ signal[target] = normalized;
116
+ }
117
+ }
118
+ if (typeof input.observed_at === 'string' && input.observed_at.trim()) {
119
+ signal.observed_at = input.observed_at.trim();
120
+ }
121
+ return signal;
122
+ }
123
+
124
+ function normalizeLease(input) {
125
+ if (input == null) return null;
126
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
127
+ throw storeError('OBSERVABILITY_STORE_CORRUPT', 'lease de observabilidade corrompida');
128
+ }
129
+ const signalSequence = sequence(input.signal_sequence);
130
+ const expiresAt = timeValue(input.expires_at, null);
131
+ if (signalSequence === null || expiresAt === null) {
132
+ throw storeError('OBSERVABILITY_STORE_CORRUPT', 'lease de observabilidade corrompida');
133
+ }
134
+ return {
135
+ owner_token: nonEmptyString(input.owner_token, 'OBSERVABILITY_STORE_CORRUPT'),
136
+ signal_sequence: signalSequence,
137
+ acquired_at: timeValue(input.acquired_at, expiresAt),
138
+ expires_at: expiresAt,
139
+ };
140
+ }
141
+
142
+ function normalizeStoreState(input, sessionId) {
143
+ if (!input || typeof input !== 'object' || Array.isArray(input)
144
+ || input.schema_version !== STORE_SCHEMA_VERSION
145
+ || input.session_id !== sessionId) {
146
+ throw storeError('OBSERVABILITY_STORE_CORRUPT', 'store de observabilidade corrompido');
147
+ }
148
+ const signalSequence = sequence(input.observability_signal_sequence);
149
+ const checkpointSequence = sequence(input.observability_checkpoint_sequence);
150
+ if (signalSequence === null || checkpointSequence === null
151
+ || checkpointSequence > signalSequence
152
+ || typeof input.observability_dirty !== 'boolean'
153
+ || !Array.isArray(input.signals)) {
154
+ throw storeError('OBSERVABILITY_STORE_CORRUPT', 'store de observabilidade corrompido');
155
+ }
156
+ const seen = new Set();
157
+ const signals = input.signals.map(normalizeSignal);
158
+ let previousSignalSequence = 0;
159
+ for (const signal of signals) {
160
+ if (seen.has(signal.rollout_id)
161
+ || !Number.isSafeInteger(signal.signal_sequence)
162
+ || signal.signal_sequence <= previousSignalSequence
163
+ || signal.signal_sequence > signalSequence) {
164
+ throw storeError('OBSERVABILITY_STORE_CORRUPT', 'store de observabilidade corrompido');
165
+ }
166
+ seen.add(signal.rollout_id);
167
+ previousSignalSequence = signal.signal_sequence;
168
+ }
169
+ return {
170
+ schema_version: STORE_SCHEMA_VERSION,
171
+ session_id: sessionId,
172
+ observability_signal_sequence: signalSequence,
173
+ observability_checkpoint_sequence: checkpointSequence,
174
+ observability_dirty: input.observability_dirty,
175
+ signals,
176
+ lease: normalizeLease(input.lease),
177
+ checkpoint_frontier: jsonClone(input.checkpoint_frontier),
178
+ source_manifest: jsonClone(input.source_manifest),
179
+ graph_cache: jsonClone(input.graph_cache),
180
+ diagnostics: sanitizeObservabilityDiagnostics(input.diagnostics ?? []),
181
+ reconstructed: Boolean(input.reconstructed),
182
+ };
183
+ }
184
+
185
+ function serializableState(input) {
186
+ return {
187
+ schema_version: input.schema_version,
188
+ session_id: input.session_id,
189
+ observability_signal_sequence: input.observability_signal_sequence,
190
+ observability_checkpoint_sequence: input.observability_checkpoint_sequence,
191
+ observability_dirty: input.observability_dirty,
192
+ signals: input.signals,
193
+ lease: input.lease,
194
+ checkpoint_frontier: input.checkpoint_frontier,
195
+ source_manifest: input.source_manifest,
196
+ graph_cache: input.graph_cache,
197
+ diagnostics: input.diagnostics,
198
+ };
199
+ }
200
+
201
+ function serialized(input) {
202
+ return `${JSON.stringify(serializableState(input), null, 2)}\n`;
203
+ }
204
+
205
+ function storeDirectory(vaultBase) {
206
+ return join(vaultBase, ...STORE_DIR_PARTS);
207
+ }
208
+
209
+ function ensureStoreDirectory(vaultBase) {
210
+ return mkdirVaultPath(vaultBase, storeDirectory(vaultBase), {
211
+ recursive: true,
212
+ label: 'runtime de observabilidade de sessão',
213
+ code: STORE_PATH_CODE,
214
+ });
215
+ }
216
+
217
+ export function observabilityStorePath(vaultBase, sessionId) {
218
+ const id = nonEmptyString(sessionId);
219
+ const digest = createHash('sha256').update(id).digest('hex');
220
+ return join(vaultBase, ...STORE_DIR_PARTS, `${digest}.json`);
221
+ }
222
+
223
+ function readStorePath(vaultBase, sessionId, path) {
224
+ const checked = assertVaultPathSafe(vaultBase, path, {
225
+ expectedType: 'file',
226
+ label: 'store de observabilidade de sessão',
227
+ code: STORE_PATH_CODE,
228
+ });
229
+ if (!checked.exists) return defaultState(sessionId);
230
+ try {
231
+ const raw = readFileSync(checked.target, 'utf8');
232
+ assertVaultPathSafe(vaultBase, checked.target, {
233
+ allowMissing: false,
234
+ expectedType: 'file',
235
+ label: 'store de observabilidade de sessão',
236
+ code: STORE_PATH_CODE,
237
+ });
238
+ return normalizeStoreState(JSON.parse(raw), sessionId);
239
+ } catch (error) {
240
+ if (error?.code === STORE_PATH_CODE) throw error;
241
+ return defaultState(sessionId, {
242
+ reconstructed: true,
243
+ dirty: true,
244
+ diagnostics: [{ code: 'CACHE_INVALID', count: 1 }],
245
+ });
246
+ }
247
+ }
248
+
249
+ export function readObservabilityStore(vaultBase, sessionId) {
250
+ const id = nonEmptyString(sessionId);
251
+ return readStorePath(vaultBase, id, observabilityStorePath(vaultBase, id));
252
+ }
253
+
254
+ /**
255
+ * Serialize a small store transition under the hardened Vault path lock.
256
+ * The mutator may return `{ state, value }`; its `value` is returned without persistence.
257
+ */
258
+ export function mutateObservabilityStore(vaultBase, sessionId, mutator, {
259
+ lockTimeoutMs = 2_000,
260
+ lockStaleMs = 10_000,
261
+ } = {}) {
262
+ const id = nonEmptyString(sessionId);
263
+ if (typeof mutator !== 'function') {
264
+ throw storeError('OBSERVABILITY_STORE_INVALID', 'mutator de observabilidade inválido');
265
+ }
266
+ ensureStoreDirectory(vaultBase);
267
+ const path = observabilityStorePath(vaultBase, id);
268
+ const outcome = withVaultPathLock(vaultBase, path, () => {
269
+ const current = readStorePath(vaultBase, id, path);
270
+ const transition = mutator(jsonClone(current));
271
+ if (transition == null) {
272
+ return { state: current, changed: false, value: null, reconstructed: current.reconstructed };
273
+ }
274
+ const nextInput = Object.hasOwn(transition, 'state') ? transition.state : transition;
275
+ const value = Object.hasOwn(transition, 'state') ? transition.value : null;
276
+ const next = normalizeStoreState({
277
+ ...nextInput,
278
+ schema_version: STORE_SCHEMA_VERSION,
279
+ session_id: id,
280
+ }, id);
281
+ next.reconstructed = false;
282
+ const before = serialized(current);
283
+ const after = serialized(next);
284
+ if (before !== after || current.reconstructed) {
285
+ writeVaultFileAtomic(vaultBase, path, after, 'utf8', {
286
+ label: 'store de observabilidade de sessão',
287
+ code: STORE_PATH_CODE,
288
+ });
289
+ }
290
+ return {
291
+ state: next,
292
+ changed: before !== after || current.reconstructed,
293
+ value,
294
+ reconstructed: current.reconstructed,
295
+ };
296
+ }, {
297
+ timeoutMs: lockTimeoutMs,
298
+ staleMs: lockStaleMs,
299
+ code: STORE_PATH_CODE,
300
+ });
301
+ if (outcome === VAULT_LOCK_BUSY) {
302
+ return { state: null, changed: false, value: null, busy: true, reconstructed: false };
303
+ }
304
+ return { ...outcome, busy: false };
305
+ }
306
+
307
+ export function recordObservabilitySignal(vaultBase, sessionId, signalInput, {
308
+ maxSignals = DEFAULT_SIGNAL_LIMIT,
309
+ ...storeOptions
310
+ } = {}) {
311
+ const signal = normalizeSignal(signalInput);
312
+ if (!Number.isSafeInteger(maxSignals) || maxSignals <= 0) {
313
+ throw storeError('OBSERVABILITY_STORE_INVALID', 'limite de sinais inválido');
314
+ }
315
+ const outcome = mutateObservabilityStore(vaultBase, sessionId, (state) => {
316
+ const duplicate = state.signals.some((entry) => entry.rollout_id === signal.rollout_id);
317
+ if (duplicate) return { state, value: { duplicate: true } };
318
+ const nextSequence = state.observability_signal_sequence + 1;
319
+ state.observability_signal_sequence = nextSequence;
320
+ state.observability_dirty = true;
321
+ state.signals = [...state.signals, { ...signal, signal_sequence: nextSequence }]
322
+ .slice(-maxSignals);
323
+ return { state, value: { duplicate: false } };
324
+ }, storeOptions);
325
+ if (outcome.busy) {
326
+ return { recorded: false, duplicate: false, sequence: null, state: null, reason: 'store-busy' };
327
+ }
328
+ return {
329
+ recorded: !outcome.value.duplicate,
330
+ duplicate: outcome.value.duplicate,
331
+ sequence: outcome.state.observability_signal_sequence,
332
+ state: outcome.state,
333
+ reason: outcome.value.duplicate ? 'duplicate' : 'recorded',
334
+ };
335
+ }
336
+
337
+ export function tryAcquireObservabilityLease(vaultBase, sessionId, {
338
+ signalSequence,
339
+ ownerToken = randomUUID(),
340
+ now = Date.now(),
341
+ ttlMs = 20_000,
342
+ ...storeOptions
343
+ } = {}) {
344
+ const requestedSequence = sequence(signalSequence);
345
+ const token = nonEmptyString(ownerToken, 'OBSERVABILITY_LEASE_INVALID');
346
+ const nowMs = timeValue(now, null);
347
+ if (requestedSequence === null || nowMs === null || !Number.isFinite(ttlMs) || ttlMs <= 0) {
348
+ throw storeError('OBSERVABILITY_LEASE_INVALID', 'lease de observabilidade inválida');
349
+ }
350
+ const outcome = mutateObservabilityStore(vaultBase, sessionId, (state) => {
351
+ const latest = state.observability_signal_sequence;
352
+ if (requestedSequence < latest) {
353
+ return { state, value: { acquired: false, reason: 'stale-signal' } };
354
+ }
355
+ if (requestedSequence > latest) {
356
+ return { state, value: { acquired: false, reason: 'future-signal' } };
357
+ }
358
+ const lease = state.lease;
359
+ if (lease && lease.expires_at > nowMs
360
+ && lease.signal_sequence === requestedSequence
361
+ && lease.owner_token !== token) {
362
+ return { state, value: { acquired: false, reason: 'lease-busy' } };
363
+ }
364
+ if (lease && lease.expires_at > nowMs
365
+ && lease.signal_sequence === requestedSequence
366
+ && lease.owner_token === token) {
367
+ return { state, value: { acquired: true, reason: 'already-owned' } };
368
+ }
369
+ state.lease = {
370
+ owner_token: token,
371
+ signal_sequence: requestedSequence,
372
+ acquired_at: nowMs,
373
+ expires_at: nowMs + ttlMs,
374
+ };
375
+ return { state, value: { acquired: true, reason: lease ? 'superseded' : 'acquired' } };
376
+ }, storeOptions);
377
+ if (outcome.busy) {
378
+ return { acquired: false, reason: 'store-busy', state: null, ownerToken: token };
379
+ }
380
+ return { ...outcome.value, state: outcome.state, ownerToken: token };
381
+ }
382
+
383
+ export function releaseObservabilityLease(vaultBase, sessionId, {
384
+ ownerToken,
385
+ signalSequence,
386
+ ...storeOptions
387
+ } = {}) {
388
+ const token = nonEmptyString(ownerToken, 'OBSERVABILITY_LEASE_INVALID');
389
+ const expectedSequence = signalSequence === undefined ? null : sequence(signalSequence);
390
+ if (signalSequence !== undefined && expectedSequence === null) {
391
+ throw storeError('OBSERVABILITY_LEASE_INVALID', 'lease de observabilidade inválida');
392
+ }
393
+ const outcome = mutateObservabilityStore(vaultBase, sessionId, (state) => {
394
+ const owned = state.lease?.owner_token === token
395
+ && (expectedSequence === null || state.lease.signal_sequence === expectedSequence);
396
+ if (!owned) return { state, value: { released: false, reason: 'not-owner' } };
397
+ state.lease = null;
398
+ return { state, value: { released: true, reason: 'released' } };
399
+ }, storeOptions);
400
+ if (outcome.busy) return { released: false, reason: 'store-busy', state: null };
401
+ return { ...outcome.value, state: outcome.state };
402
+ }
403
+
404
+ export function markObservabilityCheckpoint(vaultBase, sessionId, {
405
+ checkpointSequence,
406
+ frontier = null,
407
+ sourceManifest,
408
+ graphCache,
409
+ diagnostics,
410
+ ...storeOptions
411
+ } = {}) {
412
+ const nextCheckpoint = sequence(checkpointSequence);
413
+ if (nextCheckpoint === null) {
414
+ throw storeError('OBSERVABILITY_CHECKPOINT_INVALID', 'checkpoint de observabilidade inválido');
415
+ }
416
+ const outcome = mutateObservabilityStore(vaultBase, sessionId, (state) => {
417
+ if (nextCheckpoint > state.observability_signal_sequence) {
418
+ throw storeError('OBSERVABILITY_CHECKPOINT_INVALID', 'checkpoint de observabilidade inválido');
419
+ }
420
+ if (nextCheckpoint < state.observability_checkpoint_sequence) {
421
+ return { state, value: { accepted: false, reason: 'stale-checkpoint' } };
422
+ }
423
+ state.observability_checkpoint_sequence = nextCheckpoint;
424
+ state.observability_dirty = nextCheckpoint < state.observability_signal_sequence;
425
+ state.checkpoint_frontier = jsonClone(frontier);
426
+ if (sourceManifest !== undefined) state.source_manifest = jsonClone(sourceManifest);
427
+ if (graphCache !== undefined) state.graph_cache = jsonClone(graphCache);
428
+ if (diagnostics !== undefined) {
429
+ state.diagnostics = sanitizeObservabilityDiagnostics(diagnostics);
430
+ }
431
+ if (state.lease && state.lease.signal_sequence <= nextCheckpoint) state.lease = null;
432
+ return { state, value: { accepted: true, reason: 'checkpointed' } };
433
+ }, storeOptions);
434
+ if (outcome.busy) return null;
435
+ return outcome.state;
436
+ }