wendkeep 0.68.1 → 0.68.6
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/CHANGELOG.md +60 -0
- package/README.en.md +33 -12
- package/README.md +33 -12
- package/docs/en/commands/changes-and-verification.md +12 -0
- package/docs/en/commands/getting-started.md +12 -4
- package/docs/en/commands/memory.md +3 -1
- package/docs/en/commands/operating-profiles.md +11 -0
- package/docs/en/commands/sessions-and-import.md +15 -0
- package/docs/pt-BR/commands/changes-and-verification.md +12 -0
- package/docs/pt-BR/commands/getting-started.md +12 -4
- package/docs/pt-BR/commands/memory.md +3 -1
- package/docs/pt-BR/commands/operating-profiles.md +11 -0
- package/docs/pt-BR/commands/sessions-and-import.md +15 -0
- package/hooks/brain-core.mjs +159 -159
- package/hooks/brain-recall.mjs +32 -32
- package/hooks/brain-reindex.mjs +13 -13
- package/hooks/change-guard.mjs +142 -103
- package/hooks/git-snapshot.mjs +25 -6
- package/hooks/obsidian-common.mjs +40 -0
- package/hooks/project-scope.mjs +435 -0
- package/hooks/session-backfill.mjs +1 -1
- package/hooks/session-ensure.mjs +23 -4
- package/hooks/session-iteration-outcome.mjs +143 -0
- package/hooks/session-start.mjs +15 -0
- package/hooks/session-stop.mjs +263 -42
- package/hooks/token-usage.mjs +13 -11
- package/package.json +2 -2
- package/packages/integrations/src/host-hooks.mjs +7 -7
- package/packages/integrations/src/prompt-content.mjs +12 -0
- package/packages/integrations/src/transcripts.mjs +37 -8
- package/packages/vault/src/memory-store.mjs +63 -1
- package/src/init.mjs +3 -0
- package/src/memory.mjs +10 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
isSyntheticTranscriptText,
|
|
3
3
|
redactSecrets,
|
|
4
4
|
sanitizeAssistantMessage,
|
|
5
5
|
} from './prompt-content.mjs';
|
|
@@ -20,14 +20,8 @@ function extractContentText(content) {
|
|
|
20
20
|
.trim();
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const SYNTHETIC_EVENT_TAG = /^<\/?(?:task-notification|system-reminder|local-command-stdout|local-command-stderr|command-message|command-name|command-args|user-prompt-submit-hook|ide_selection|ide_opened_file|environment_context)\b/i;
|
|
24
|
-
|
|
25
23
|
function shouldIgnoreUserText(text) {
|
|
26
|
-
|
|
27
|
-
return SYNTHETIC_EVENT_TAG.test(trimmed)
|
|
28
|
-
|| isBootstrapPrompt(trimmed)
|
|
29
|
-
|| /^Generate a concise( UI)? title/i.test(trimmed)
|
|
30
|
-
|| /^You are a helpful assistant\. You will be presented with a user prompt/i.test(trimmed);
|
|
24
|
+
return isSyntheticTranscriptText(text);
|
|
31
25
|
}
|
|
32
26
|
|
|
33
27
|
function addUnique(list, value) {
|
|
@@ -180,6 +174,16 @@ export function completedCodexTurnIdsContent(content = '') {
|
|
|
180
174
|
return completed;
|
|
181
175
|
}
|
|
182
176
|
|
|
177
|
+
export function abortedCodexTurnIdsContent(content = '') {
|
|
178
|
+
const aborted = new Set();
|
|
179
|
+
for (const event of jsonLines(content)) {
|
|
180
|
+
if (event.type !== 'event_msg' || event.payload?.type !== 'turn_aborted') continue;
|
|
181
|
+
const turnId = String(event.payload?.turn_id || event.turn_id || '').trim();
|
|
182
|
+
if (turnId) aborted.add(turnId);
|
|
183
|
+
}
|
|
184
|
+
return aborted;
|
|
185
|
+
}
|
|
186
|
+
|
|
183
187
|
export function parseCodexTranscriptContent(content, options = {}) {
|
|
184
188
|
const result = createResult('codex');
|
|
185
189
|
const eventUserPrompts = [];
|
|
@@ -208,6 +212,11 @@ export function parseCodexTranscriptContent(content, options = {}) {
|
|
|
208
212
|
ensureTurn(result.latestTurnId, event.timestamp);
|
|
209
213
|
continue;
|
|
210
214
|
}
|
|
215
|
+
if (event.type === 'event_msg' && event.payload?.type === 'turn_aborted') {
|
|
216
|
+
const turn = ensureTurn(event.payload.turn_id || event.turn_id || result.latestTurnId, event.timestamp);
|
|
217
|
+
turn.status = 'aborted';
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
211
220
|
if (event.type === 'turn_context') {
|
|
212
221
|
result.latestTurnId = event.payload?.turn_id || result.latestTurnId;
|
|
213
222
|
result.model = event.payload?.model || result.model;
|
|
@@ -280,6 +289,26 @@ export function parseCodexTranscriptContent(content, options = {}) {
|
|
|
280
289
|
}
|
|
281
290
|
}
|
|
282
291
|
}
|
|
292
|
+
if (payload.type === 'custom_tool_call') {
|
|
293
|
+
const name = payload.name || payload.tool_name || payload.tool || 'custom_tool_call';
|
|
294
|
+
const turn = ensureTurn(payload.turn_id || event.turn_id || result.latestTurnId, event.timestamp);
|
|
295
|
+
addUnique(result.tools, name);
|
|
296
|
+
addUnique(turn.tools, name);
|
|
297
|
+
const parsed = parseToolArguments(payload.arguments ?? payload.input ?? payload.parameters);
|
|
298
|
+
const combined = typeof parsed.raw === 'string' ? parsed.raw : toolArgumentText(parsed);
|
|
299
|
+
for (const path of extractPaths(combined, paths)) {
|
|
300
|
+
addUnique(result.consultedFiles, path);
|
|
301
|
+
addUnique(turn.consultedFiles, path);
|
|
302
|
+
}
|
|
303
|
+
for (const path of extractPatchFiles(combined)) {
|
|
304
|
+
addUnique(result.changedFiles, path);
|
|
305
|
+
addUnique(turn.changedFiles, path);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (payload.type === 'custom_tool_call_output') {
|
|
309
|
+
// A custom tool output closes an existing call; it is deliberately not a second tool.
|
|
310
|
+
ensureTurn(payload.turn_id || event.turn_id || result.latestTurnId, event.timestamp);
|
|
311
|
+
}
|
|
283
312
|
if (payload.type === 'tool_search_call') {
|
|
284
313
|
const turn = ensureTurn(payload.turn_id || event.turn_id || result.latestTurnId, event.timestamp);
|
|
285
314
|
addUnique(result.tools, 'tool_search');
|
|
@@ -446,6 +446,27 @@ function currentEventFromRecord(record) {
|
|
|
446
446
|
return { ...record.source, value: record.value, revision: record.revision };
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
+
function supersededTransitively(superseded, sourceEventId, finalEventId) {
|
|
450
|
+
if (!sourceEventId || !finalEventId || sourceEventId === finalEventId) return false;
|
|
451
|
+
const edges = new Map();
|
|
452
|
+
for (const item of superseded) {
|
|
453
|
+
if (!edges.has(item.event_id)) edges.set(item.event_id, []);
|
|
454
|
+
edges.get(item.event_id).push(item.by_event_id);
|
|
455
|
+
}
|
|
456
|
+
const pending = [sourceEventId];
|
|
457
|
+
const visited = new Set();
|
|
458
|
+
while (pending.length) {
|
|
459
|
+
const current = pending.shift();
|
|
460
|
+
if (visited.has(current)) continue;
|
|
461
|
+
visited.add(current);
|
|
462
|
+
for (const next of edges.get(current) || []) {
|
|
463
|
+
if (next === finalEventId) return true;
|
|
464
|
+
if (!visited.has(next)) pending.push(next);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
|
|
449
470
|
function isCausallyOlder(event, current) {
|
|
450
471
|
if (!current) return false;
|
|
451
472
|
if (sameCausalActivation(event, current)) {
|
|
@@ -636,6 +657,24 @@ export function reduceMemoryEvents(inputEvents = [], {
|
|
|
636
657
|
}
|
|
637
658
|
records.set(item.memory_key, { value: item.value, revision: current.revision + 1, source: item });
|
|
638
659
|
tombstones.delete(item.memory_key);
|
|
660
|
+
const explicitlySupersededIds = new Set(
|
|
661
|
+
Array.isArray(item.supersedes)
|
|
662
|
+
? item.supersedes
|
|
663
|
+
: (item.supersedes_event_id ? [item.supersedes_event_id] : []),
|
|
664
|
+
);
|
|
665
|
+
if (item.candidate_decision?.action === 'promote' && explicitlySupersededIds.size) {
|
|
666
|
+
for (const candidate of candidates) {
|
|
667
|
+
if (candidate.reason !== 'conflict' || !candidate.event_ids?.length) continue;
|
|
668
|
+
if (candidate.event_ids.every((eventId) => explicitlySupersededIds.has(eventId))) {
|
|
669
|
+
resolvedCandidateIds.add(candidate.candidate_id);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
for (const pending of pendingAssertConflicts) {
|
|
673
|
+
if (explicitlySupersededIds.has(pending.event.event_id)) {
|
|
674
|
+
resolvedCandidateIds.add(pending.candidate.candidate_id);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
639
678
|
revision += 1;
|
|
640
679
|
appliedEventIds.push(item.event_id);
|
|
641
680
|
}
|
|
@@ -680,13 +719,36 @@ export function reduceMemoryEvents(inputEvents = [], {
|
|
|
680
719
|
}
|
|
681
720
|
}
|
|
682
721
|
|
|
722
|
+
const pendingByCandidateId = new Map(
|
|
723
|
+
pendingAssertConflicts.map((pending) => [pending.candidate.candidate_id, pending]),
|
|
724
|
+
);
|
|
725
|
+
const reanchoredCandidates = candidates
|
|
726
|
+
.map((candidate) => {
|
|
727
|
+
const pending = pendingByCandidateId.get(candidate.candidate_id);
|
|
728
|
+
if (!pending) return candidate;
|
|
729
|
+
const finalSource = currentEventFromRecord(records.get(candidate.memory_key));
|
|
730
|
+
const previousSource = candidate.events?.find(
|
|
731
|
+
(event) => event.event_id !== pending.event.event_id,
|
|
732
|
+
);
|
|
733
|
+
if (!finalSource || !previousSource || finalSource.event_id === previousSource.event_id) {
|
|
734
|
+
return candidate;
|
|
735
|
+
}
|
|
736
|
+
if (!sameCompleteCausalLineage(previousSource, finalSource)
|
|
737
|
+
|| !supersededTransitively(superseded, previousSource.event_id, finalSource.event_id)) {
|
|
738
|
+
return candidate;
|
|
739
|
+
}
|
|
740
|
+
if (hashMemoryValue(finalSource.value) === hashMemoryValue(pending.event.value)) return null;
|
|
741
|
+
return conflictCandidate(candidate.memory_key, [finalSource, pending.event], finalSource);
|
|
742
|
+
})
|
|
743
|
+
.filter(Boolean);
|
|
744
|
+
|
|
683
745
|
const stateEntries = [...records].map(([key, record]) => [key, record.value]);
|
|
684
746
|
const recordEntries = [...records].map(([key, record]) => [key, record]);
|
|
685
747
|
const tombstoneEntries = [...tombstones];
|
|
686
748
|
const state = sortedObject(stateEntries);
|
|
687
749
|
const recordObject = sortedObject(recordEntries);
|
|
688
750
|
const tombstoneObject = sortedObject(tombstoneEntries);
|
|
689
|
-
const unresolvedCandidates =
|
|
751
|
+
const unresolvedCandidates = reanchoredCandidates
|
|
690
752
|
.filter((item) => !candidateDecisions.has(item.candidate_id)
|
|
691
753
|
&& !resolvedCandidateIds.has(item.candidate_id));
|
|
692
754
|
unresolvedCandidates.sort((left, right) => left.candidate_id.localeCompare(right.candidate_id));
|
package/src/init.mjs
CHANGED
|
@@ -220,6 +220,7 @@ export function mergeCodexHooks(existing, { force = false } = {}) {
|
|
|
220
220
|
const owning = groups.find((g) => (g.hooks || []).some(owns));
|
|
221
221
|
if (owning) {
|
|
222
222
|
const hk = owning.hooks.find(owns);
|
|
223
|
+
const matcher = CODEX_MATCHER_EVENTS.has(h.event) ? h.matcher : null;
|
|
223
224
|
// `timeout` is the pre-0.46 key: Codex never read it. Migrate it even without --force,
|
|
224
225
|
// otherwise the hook keeps running at the 600s default forever.
|
|
225
226
|
const legacyTimeout = 'timeout' in hk;
|
|
@@ -233,6 +234,8 @@ export function mergeCodexHooks(existing, { force = false } = {}) {
|
|
|
233
234
|
if (entry.statusMessage) hk.statusMessage = entry.statusMessage;
|
|
234
235
|
}
|
|
235
236
|
}
|
|
237
|
+
if (force && matcher) owning.matcher = matcher;
|
|
238
|
+
if (force && !matcher) delete owning.matcher;
|
|
236
239
|
file.hooks[h.event] = groups;
|
|
237
240
|
continue;
|
|
238
241
|
}
|
package/src/memory.mjs
CHANGED
|
@@ -387,9 +387,16 @@ function promotedSupersedes(vault, candidate, selected) {
|
|
|
387
387
|
const memberIds = new Set(Array.isArray(candidate.event_ids) ? candidate.event_ids : []);
|
|
388
388
|
const ledger = readMemoryLedger(vault);
|
|
389
389
|
if (ledger.status !== 'ok') throw new Error('Ledger de memória inválido durante a promoção.');
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
if (!current?.event_id
|
|
390
|
+
const projection = deriveMemoryProjection(vault, ledger.events);
|
|
391
|
+
const current = projection.records?.[candidate.memory_key]?.source;
|
|
392
|
+
if (!current?.event_id) return [...memberIds].sort();
|
|
393
|
+
if (memberIds.has(current.event_id)) {
|
|
394
|
+
projection.superseded
|
|
395
|
+
.filter((item) => item.by_event_id === current.event_id)
|
|
396
|
+
.map((item) => item.event_id)
|
|
397
|
+
.forEach((eventId) => memberIds.add(eventId));
|
|
398
|
+
return [...memberIds].sort();
|
|
399
|
+
}
|
|
393
400
|
|
|
394
401
|
const currentSelectedId = current.candidate_decision?.selected_event_id;
|
|
395
402
|
const currentSelected = currentSelectedId
|