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,129 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { statSync } from 'node:fs';
3
+ import { readCodexRolloutMeta } from './codex-rollout-meta.mjs';
4
+
5
+ const stableHash = (value) => createHash('sha256').update(JSON.stringify(value)).digest('hex');
6
+
7
+ function addTranscriptPath(paths, value) {
8
+ if (typeof value === 'string' && value.trim()) paths.add(value.trim());
9
+ }
10
+
11
+ function collectActivationPaths(paths, activations) {
12
+ if (!activations || typeof activations !== 'object') return;
13
+ const values = Array.isArray(activations) ? activations : Object.values(activations);
14
+ for (const activation of values) {
15
+ if (!activation || typeof activation !== 'object') continue;
16
+ addTranscriptPath(paths, activation.transcript_path);
17
+ for (const path of activation.transcript_paths || []) addTranscriptPath(paths, path);
18
+ }
19
+ }
20
+
21
+ function isCodexDescendant(meta) {
22
+ return Boolean(meta?.source && typeof meta.source === 'object' && meta.source.subagent);
23
+ }
24
+
25
+ // Resolve every transcript explicitly attached to the registry entry. Classification is
26
+ // authoritative only when the rollout's first session_meta line can be read: filename and
27
+ // directory layout are deliberately not used as subagent heuristics.
28
+ export function resolveObservabilityRoots(entry, { readMeta = readCodexRolloutMeta } = {}) {
29
+ if (!entry || typeof entry !== 'object') {
30
+ return {
31
+ state: 'degraded',
32
+ rootPaths: [],
33
+ descendantPaths: [],
34
+ diagnostics: [{ code: 'MAIN_TRANSCRIPT_UNRESOLVED', count: 1 }],
35
+ };
36
+ }
37
+
38
+ const candidates = new Set();
39
+ addTranscriptPath(candidates, entry.transcript_path);
40
+ for (const path of entry.transcript_paths || []) addTranscriptPath(candidates, path);
41
+ collectActivationPaths(candidates, entry.activations);
42
+
43
+ const rootPaths = [];
44
+ const descendantPaths = [];
45
+ let unreadable = 0;
46
+ for (const path of [...candidates].sort((a, b) => a.localeCompare(b))) {
47
+ const result = readMeta(path);
48
+ if (!result?.ok) {
49
+ unreadable += 1;
50
+ continue;
51
+ }
52
+ if (isCodexDescendant(result.meta)) descendantPaths.push(path);
53
+ else rootPaths.push(path);
54
+ }
55
+
56
+ if (unreadable > 0) {
57
+ return {
58
+ state: 'degraded',
59
+ rootPaths,
60
+ descendantPaths,
61
+ diagnostics: [{ code: 'MAIN_TRANSCRIPT_UNRESOLVED', count: unreadable }],
62
+ };
63
+ }
64
+
65
+ return { state: 'complete', rootPaths, descendantPaths, diagnostics: [] };
66
+ }
67
+
68
+ function sameFrontier(left, right) {
69
+ return Boolean(left && right && JSON.stringify(left) === JSON.stringify(right));
70
+ }
71
+
72
+ export function assessObservabilityFreshness({
73
+ checkpoint,
74
+ runtimeState,
75
+ statSource = statSync,
76
+ } = {}) {
77
+ if (!checkpoint) return { fresh: false, status: 'legacy', diagnostics: [] };
78
+ if (checkpoint.state === 'degraded') {
79
+ return {
80
+ fresh: false,
81
+ status: 'degraded',
82
+ diagnostics: checkpoint.diagnostics || [],
83
+ };
84
+ }
85
+ const manifest = runtimeState?.source_manifest;
86
+ if (!Array.isArray(manifest) || manifest.length === 0) {
87
+ return { fresh: false, status: 'manifest-unproven', diagnostics: [] };
88
+ }
89
+
90
+ const hashInput = [];
91
+ for (const source of manifest) {
92
+ if (!source || typeof source.path !== 'string' || typeof source.rolloutId !== 'string') {
93
+ return { fresh: false, status: 'manifest-unproven', diagnostics: [] };
94
+ }
95
+ let stat;
96
+ try { stat = statSource(source.path); } catch {
97
+ return {
98
+ fresh: false,
99
+ status: 'stale',
100
+ diagnostics: [{ code: 'STALE_FRONTIER', count: 1 }],
101
+ };
102
+ }
103
+ if (!stat.isFile() || stat.size !== Number(source.size)
104
+ || stat.mtimeMs !== Number(source.mtimeMs)) {
105
+ return {
106
+ fresh: false,
107
+ status: 'stale',
108
+ diagnostics: [{ code: 'STALE_FRONTIER', count: 1 }],
109
+ };
110
+ }
111
+ hashInput.push({ rolloutId: source.rolloutId, size: stat.size, mtimeMs: stat.mtimeMs });
112
+ }
113
+ hashInput.sort((left, right) =>
114
+ `${left.rolloutId}\u0000${left.size}\u0000${left.mtimeMs}`.localeCompare(
115
+ `${right.rolloutId}\u0000${right.size}\u0000${right.mtimeMs}`,
116
+ ));
117
+ const stale = stableHash(hashInput) !== checkpoint.frontier.source_manifest_hash
118
+ || !sameFrontier(runtimeState?.checkpoint_frontier, checkpoint.frontier)
119
+ || runtimeState?.observability_dirty !== false
120
+ || runtimeState?.observability_checkpoint_sequence !== runtimeState?.observability_signal_sequence
121
+ || runtimeState?.observability_checkpoint_sequence !== checkpoint.frontier.signal_sequence;
122
+ return stale
123
+ ? {
124
+ fresh: false,
125
+ status: 'stale',
126
+ diagnostics: [{ code: 'STALE_FRONTIER', count: 1 }],
127
+ }
128
+ : { fresh: true, status: 'fresh', diagnostics: [] };
129
+ }
@@ -0,0 +1,241 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ export const OBSERVABILITY_SCHEMA = 2;
4
+
5
+ export const OBSERVABILITY_DIAGNOSTIC_CODES = Object.freeze([
6
+ 'CACHE_INVALID',
7
+ 'CHILD_META_INVALID',
8
+ 'CHILD_MISSING',
9
+ 'DUPLICATE_ROLLOUT_ID',
10
+ 'FALLBACK_LIMIT_EXCEEDED',
11
+ 'GRAPH_LIMIT_EXCEEDED',
12
+ 'LEGACY_CHAIN_UNPROVEN',
13
+ 'LIVE_BYTE_BUDGET_EXCEEDED',
14
+ 'LIVE_DEADLINE_EXCEEDED',
15
+ 'MAIN_TRANSCRIPT_UNRESOLVED',
16
+ 'PARENT_META_INVALID',
17
+ 'ROOT_MISMATCH',
18
+ 'SOURCE_CHANGED_DURING_SCAN',
19
+ 'STALE_FRONTIER',
20
+ ]);
21
+
22
+ const DIAGNOSTIC_CODE_SET = new Set(OBSERVABILITY_DIAGNOSTIC_CODES);
23
+ const OBSERVABILITY_STATES = new Set(['complete', 'none', 'degraded']);
24
+ const FRONTIER_STRING_FIELDS = [
25
+ 'canonical_session_id',
26
+ 'activation_id',
27
+ 'roots_stat_hash',
28
+ 'graph_cursor',
29
+ 'source_manifest_hash',
30
+ ];
31
+ const FRONTIER_SEQUENCE_FIELDS = [
32
+ 'activation_epoch',
33
+ 'turn_sequence',
34
+ 'signal_sequence',
35
+ ];
36
+ const FRONTMATTER_TO_FRONTIER = Object.freeze({
37
+ canonical_session_id: 'observability_session_id',
38
+ activation_id: 'observability_activation_id',
39
+ activation_epoch: 'observability_activation_epoch',
40
+ turn_sequence: 'observability_turn_sequence',
41
+ signal_sequence: 'observability_signal_sequence',
42
+ roots_stat_hash: 'observability_roots_stat_hash',
43
+ graph_cursor: 'observability_graph_cursor',
44
+ source_manifest_hash: 'observability_source_manifest_hash',
45
+ });
46
+
47
+ function diagnosticError() {
48
+ const error = new TypeError('diagnostic de observabilidade inválido');
49
+ error.code = 'OBSERVABILITY_DIAGNOSTIC_INVALID';
50
+ return error;
51
+ }
52
+
53
+ function frontierError() {
54
+ const error = new TypeError('frontier de observabilidade inválido');
55
+ error.code = 'OBSERVABILITY_FRONTIER_INVALID';
56
+ return error;
57
+ }
58
+
59
+ function checkpointError() {
60
+ const error = new TypeError('checkpoint de observabilidade inválido');
61
+ error.code = 'OBSERVABILITY_CHECKPOINT_INVALID';
62
+ return error;
63
+ }
64
+
65
+ function safeNonEmptyString(value) {
66
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
67
+ }
68
+
69
+ function safeSequence(value) {
70
+ if (typeof value === 'string' && /^\d+$/.test(value.trim())) value = Number(value);
71
+ return Number.isSafeInteger(value) && value >= 0 ? value : null;
72
+ }
73
+
74
+ /**
75
+ * Reduce diagnostics to the only shape that may cross the local runtime boundary.
76
+ * Values are never interpolated in thrown errors so rejected private data is not echoed.
77
+ */
78
+ export function sanitizeObservabilityDiagnostics(diagnostics = []) {
79
+ if (!Array.isArray(diagnostics)) throw diagnosticError();
80
+ const counts = new Map();
81
+ for (const diagnostic of diagnostics) {
82
+ if (!diagnostic || typeof diagnostic !== 'object' || Array.isArray(diagnostic)) {
83
+ throw diagnosticError();
84
+ }
85
+ const keys = Object.keys(diagnostic).sort();
86
+ if (keys.length !== 2 || keys[0] !== 'code' || keys[1] !== 'count') {
87
+ throw diagnosticError();
88
+ }
89
+ if (!DIAGNOSTIC_CODE_SET.has(diagnostic.code)
90
+ || !Number.isSafeInteger(diagnostic.count)
91
+ || diagnostic.count <= 0) {
92
+ throw diagnosticError();
93
+ }
94
+ counts.set(diagnostic.code, (counts.get(diagnostic.code) || 0) + diagnostic.count);
95
+ }
96
+ return [...counts.entries()]
97
+ .sort(([left], [right]) => left.localeCompare(right))
98
+ .map(([code, count]) => ({ code, count }));
99
+ }
100
+
101
+ export function normalizeObservabilityFrontier(input) {
102
+ if (!input || typeof input !== 'object' || Array.isArray(input)) throw frontierError();
103
+ const frontier = {};
104
+ for (const field of FRONTIER_STRING_FIELDS) {
105
+ const value = safeNonEmptyString(input[field]);
106
+ if (value === null) throw frontierError();
107
+ frontier[field] = value;
108
+ }
109
+ for (const field of FRONTIER_SEQUENCE_FIELDS) {
110
+ const value = safeSequence(input[field]);
111
+ if (value === null) throw frontierError();
112
+ frontier[field] = value;
113
+ }
114
+ return {
115
+ canonical_session_id: frontier.canonical_session_id,
116
+ activation_id: frontier.activation_id,
117
+ activation_epoch: frontier.activation_epoch,
118
+ turn_sequence: frontier.turn_sequence,
119
+ signal_sequence: frontier.signal_sequence,
120
+ roots_stat_hash: frontier.roots_stat_hash,
121
+ graph_cursor: frontier.graph_cursor,
122
+ source_manifest_hash: frontier.source_manifest_hash,
123
+ };
124
+ }
125
+
126
+ export const buildObservabilityFrontier = normalizeObservabilityFrontier;
127
+
128
+ export function hashObservabilityFrontier(input) {
129
+ const frontier = normalizeObservabilityFrontier(input);
130
+ return createHash('sha256').update(JSON.stringify(frontier)).digest('hex');
131
+ }
132
+
133
+ /**
134
+ * Compare a candidate frontier with the checkpoint already materialized.
135
+ * The argument order is deliberately (current, candidate), matching a CAS writer.
136
+ */
137
+ export function compareObservabilityFrontiers(currentInput, candidateInput) {
138
+ if (!currentInput) {
139
+ normalizeObservabilityFrontier(candidateInput);
140
+ return 'newer';
141
+ }
142
+ const current = normalizeObservabilityFrontier(currentInput);
143
+ const candidate = normalizeObservabilityFrontier(candidateInput);
144
+ if (current.canonical_session_id !== candidate.canonical_session_id) return 'conflict';
145
+
146
+ for (const field of FRONTIER_SEQUENCE_FIELDS) {
147
+ if (candidate[field] < current[field]) return 'stale';
148
+ if (candidate[field] > current[field]) return 'newer';
149
+ if (field === 'activation_epoch' && candidate.activation_id !== current.activation_id) {
150
+ return 'conflict';
151
+ }
152
+ }
153
+
154
+ for (const field of ['roots_stat_hash', 'graph_cursor', 'source_manifest_hash']) {
155
+ if (candidate[field] !== current[field]) return 'conflict';
156
+ }
157
+ return 'same';
158
+ }
159
+
160
+ export const compareObservabilityFrontier = compareObservabilityFrontiers;
161
+
162
+ function unquoteFrontmatterValue(raw) {
163
+ const value = String(raw ?? '').trim();
164
+ if (value.length >= 2 && value.startsWith("'") && value.endsWith("'")) {
165
+ return value.slice(1, -1).replaceAll("''", "'");
166
+ }
167
+ if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
168
+ try { return JSON.parse(value); } catch { return value.slice(1, -1); }
169
+ }
170
+ return value;
171
+ }
172
+
173
+ function checkpointFields(input) {
174
+ if (typeof input !== 'string') return input;
175
+ const normalized = input.replaceAll('\r\n', '\n');
176
+ const frontmatter = normalized.match(/^---\n([\s\S]*?)\n---(?:\n|$)/)?.[1] ?? normalized;
177
+ const fields = {};
178
+ for (const line of frontmatter.split('\n')) {
179
+ const match = line.match(/^([A-Za-z0-9_]+):\s*(.*)$/);
180
+ if (match) fields[match[1]] = unquoteFrontmatterValue(match[2]);
181
+ }
182
+ return fields;
183
+ }
184
+
185
+ export function renderObservabilityCheckpoint(frontierInput, {
186
+ state,
187
+ diagnostics = [],
188
+ } = {}) {
189
+ const frontier = normalizeObservabilityFrontier(frontierInput);
190
+ if (!OBSERVABILITY_STATES.has(state)) throw checkpointError();
191
+ const safeDiagnostics = sanitizeObservabilityDiagnostics(diagnostics);
192
+ return {
193
+ observability_schema: OBSERVABILITY_SCHEMA,
194
+ subagents_observability_state: state,
195
+ observability_session_id: frontier.canonical_session_id,
196
+ observability_activation_id: frontier.activation_id,
197
+ observability_activation_epoch: frontier.activation_epoch,
198
+ observability_turn_sequence: frontier.turn_sequence,
199
+ observability_signal_sequence: frontier.signal_sequence,
200
+ observability_roots_stat_hash: frontier.roots_stat_hash,
201
+ observability_graph_cursor: frontier.graph_cursor,
202
+ observability_source_manifest_hash: frontier.source_manifest_hash,
203
+ subagents_diagnostics_json: JSON.stringify(safeDiagnostics),
204
+ };
205
+ }
206
+
207
+ function quoteFrontmatter(value) {
208
+ if (typeof value === 'number') return String(value);
209
+ return `'${String(value).replaceAll("'", "''")}'`;
210
+ }
211
+
212
+ export function renderObservabilityCheckpointLines(frontierInput, options = {}) {
213
+ const fields = renderObservabilityCheckpoint(frontierInput, options);
214
+ return Object.entries(fields).map(([key, value]) => `${key}: ${quoteFrontmatter(value)}`).join('\n');
215
+ }
216
+
217
+ export function parseObservabilityCheckpoint(input) {
218
+ const fields = checkpointFields(input);
219
+ if (!fields || typeof fields !== 'object') return null;
220
+ if (safeSequence(fields.observability_schema) !== OBSERVABILITY_SCHEMA) return null;
221
+ if (!OBSERVABILITY_STATES.has(fields.subagents_observability_state)) return null;
222
+ try {
223
+ const source = {};
224
+ for (const [field, frontmatterKey] of Object.entries(FRONTMATTER_TO_FRONTIER)) {
225
+ source[field] = unquoteFrontmatterValue(fields[frontmatterKey]);
226
+ }
227
+ const frontier = normalizeObservabilityFrontier(source);
228
+ const diagnosticsRaw = unquoteFrontmatterValue(fields.subagents_diagnostics_json ?? '[]');
229
+ const diagnostics = sanitizeObservabilityDiagnostics(JSON.parse(diagnosticsRaw));
230
+ return {
231
+ schema: OBSERVABILITY_SCHEMA,
232
+ state: fields.subagents_observability_state,
233
+ frontier,
234
+ diagnostics,
235
+ };
236
+ } catch {
237
+ return null;
238
+ }
239
+ }
240
+
241
+ export const readObservabilityCheckpoint = parseObservabilityCheckpoint;