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.
- package/CHANGELOG.md +50 -0
- package/README.en.md +78 -5
- package/README.md +78 -5
- package/docs/en/commands/costs-and-observability.md +21 -7
- package/docs/en/commands/maintenance-and-diagnostics.md +13 -1
- package/docs/en/commands/operating-profiles.md +65 -10
- package/docs/en/commands/sessions-and-import.md +22 -1
- package/docs/en/commands/verify.md +5 -3
- package/docs/pt-BR/commands/costs-and-observability.md +21 -7
- package/docs/pt-BR/commands/maintenance-and-diagnostics.md +12 -1
- package/docs/pt-BR/commands/operating-profiles.md +66 -11
- package/docs/pt-BR/commands/sessions-and-import.md +20 -0
- package/docs/pt-BR/commands/verify.md +6 -3
- package/hooks/change-nag.mjs +8 -0
- package/hooks/codex-rollout-meta.mjs +112 -0
- package/hooks/codex-subagent-graph.mjs +903 -0
- package/hooks/harness-doctor.mjs +82 -1
- package/hooks/import-sessions.mjs +185 -50
- package/hooks/operating-profile-runtime.mjs +36 -2
- package/hooks/operating-profile-task-store.mjs +77 -0
- package/hooks/session-identity.mjs +40 -5
- package/hooks/session-observability-lifecycle.mjs +129 -0
- package/hooks/session-observability-state.mjs +241 -0
- package/hooks/session-observability-store.mjs +436 -0
- package/hooks/session-observability.mjs +647 -21
- package/hooks/session-stop.mjs +339 -11
- package/hooks/subagent-stop.mjs +266 -12
- package/hooks/subagent-usage.mjs +65 -0
- package/hooks/token-usage.mjs +81 -4
- package/package.json +3 -3
- package/packages/harness/src/operating-profile.mjs +127 -0
- package/packages/harness/src/sensors-core.mjs +41 -1
- package/packages/integrations/src/prompt-content.mjs +123 -0
- package/packages/integrations/src/transcripts.mjs +16 -10
- package/src/cost.mjs +40 -6
- package/src/doctor.mjs +4 -1
- package/src/profile.mjs +95 -17
- package/src/rebuild-costs.mjs +220 -34
- package/src/skills-seed.mjs +38 -2
- package/src/sync-defs.mjs +6 -1
package/hooks/session-stop.mjs
CHANGED
|
@@ -8,8 +8,13 @@ import { addUsage, costBreakdown, emptyTokenUsage, normalizeClaudeUsage, normali
|
|
|
8
8
|
import { buildBrainDigest, buildBrainIndex } from './brain-core.mjs';
|
|
9
9
|
import { activeChangeLink, pruneChangeSentinels } from './change-core.mjs';
|
|
10
10
|
import { getLocale } from './locale.mjs';
|
|
11
|
-
import {
|
|
11
|
+
import { materializeSessionObservability } from './session-observability.mjs';
|
|
12
12
|
import { resolveSessionEntry } from './session-identity.mjs';
|
|
13
|
+
import { resolveObservabilityRoots } from './session-observability-lifecycle.mjs';
|
|
14
|
+
import {
|
|
15
|
+
markObservabilityCheckpoint,
|
|
16
|
+
readObservabilityStore,
|
|
17
|
+
} from './session-observability-store.mjs';
|
|
13
18
|
import { mutateSessionNote } from './session-note-io.mjs';
|
|
14
19
|
import { applyDerivedSections, provenanceSessions } from './derived-sections.mjs';
|
|
15
20
|
import { buildSessionMemoryEvents, collectLifecycleEvidence } from './memory-handoff.mjs';
|
|
@@ -28,6 +33,7 @@ import {
|
|
|
28
33
|
parseTranscriptContent,
|
|
29
34
|
resolveTurnIdentity,
|
|
30
35
|
} from '../packages/integrations/src/transcripts.mjs';
|
|
36
|
+
import { sanitizeAssistantMessage } from '../packages/integrations/src/prompt-content.mjs';
|
|
31
37
|
export { resolveTurnIdentity };
|
|
32
38
|
import {
|
|
33
39
|
ensureDir,
|
|
@@ -208,6 +214,13 @@ function escapeMarkdownBackticks(text) {
|
|
|
208
214
|
return escaped;
|
|
209
215
|
}
|
|
210
216
|
|
|
217
|
+
function escapeMarkdownHtmlTags(text) {
|
|
218
|
+
return String(text || '').replace(
|
|
219
|
+
/<(\/?[\p{L}][\p{L}\p{N}_.-]*)(?=[\s/>])([^<>\n]*)>/gu,
|
|
220
|
+
'<$1$2>',
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
211
224
|
function compactText(text, max = 600) {
|
|
212
225
|
const clean = redactSecrets(String(text || ''))
|
|
213
226
|
.replace(/\r/g, '\n')
|
|
@@ -218,7 +231,8 @@ function compactText(text, max = 600) {
|
|
|
218
231
|
const clipped = truncate(source, max);
|
|
219
232
|
// Um corte no meio de código inline/fence pode casar com backticks da próxima
|
|
220
233
|
// entrada gerada. Só snippets realmente truncados perdem a formatação incompleta.
|
|
221
|
-
|
|
234
|
+
const markdownSafe = compact.length > max ? escapeMarkdownBackticks(clipped) : clipped;
|
|
235
|
+
return escapeMarkdownHtmlTags(markdownSafe);
|
|
222
236
|
}
|
|
223
237
|
|
|
224
238
|
function selectTurn(tx, turnId) {
|
|
@@ -230,6 +244,11 @@ function selectTurn(tx, turnId) {
|
|
|
230
244
|
|
|
231
245
|
function formatConversation(turn) {
|
|
232
246
|
const entries = (turn.conversation || [])
|
|
247
|
+
.map((entry) => (
|
|
248
|
+
entry.role === 'Assistente'
|
|
249
|
+
? { ...entry, text: sanitizeAssistantMessage(entry.text) }
|
|
250
|
+
: entry
|
|
251
|
+
))
|
|
233
252
|
.filter((entry) => entry.text && !shouldIgnoreUserText(entry.text));
|
|
234
253
|
if (!entries.length) return '- Nenhuma mensagem útil capturada no transcript.';
|
|
235
254
|
|
|
@@ -299,7 +318,9 @@ export function buildIterationBlock(tx, input) {
|
|
|
299
318
|
const now = Number.isFinite(parsedDate.getTime()) ? parsedDate : new Date();
|
|
300
319
|
const promptText = turn.userPrompts.at(-1) || tx.latestUserPrompt || '';
|
|
301
320
|
const latestAssistant = turn.assistantMessages.at(-1) || tx.latestAssistantMessage || '';
|
|
302
|
-
const heading =
|
|
321
|
+
const heading = escapeMarkdownHtmlTags(
|
|
322
|
+
truncate(promptText.replace(/[\r\n#]+/g, ' ').replace(/\s+/g, ' ').trim() || 'Iteração', 80),
|
|
323
|
+
);
|
|
303
324
|
const files = [...new Set([...(turn.consultedFiles || []), ...(turn.changedFiles || [])])];
|
|
304
325
|
const model = turn.model || tx.model || '';
|
|
305
326
|
|
|
@@ -320,7 +341,7 @@ ${formatConversation(turn)}
|
|
|
320
341
|
|
|
321
342
|
**Arquivos detectados no turno:** ${formatInlineList(files, 'Nenhum arquivo detectado automaticamente.')}
|
|
322
343
|
|
|
323
|
-
**Estado ao final do turno:** ${compactText(latestAssistant || 'Checkpoint registrado automaticamente ao final do turno.', 900)}
|
|
344
|
+
**Estado ao final do turno:** ${compactText(sanitizeAssistantMessage(latestAssistant) || 'Checkpoint registrado automaticamente ao final do turno.', 900)}
|
|
324
345
|
`;
|
|
325
346
|
}
|
|
326
347
|
|
|
@@ -644,6 +665,104 @@ function replaceClosingSection(content, closing) {
|
|
|
644
665
|
return `${content.slice(0, index).trimEnd()}\n\n${closing}\n`;
|
|
645
666
|
}
|
|
646
667
|
|
|
668
|
+
const GENERATED_ITERATION_LINE_RULES = [
|
|
669
|
+
{ pattern: /^(### \d{2}:\d{2} - )(.*)$/u, assistant: false },
|
|
670
|
+
{ pattern: /^(\*\*Pedido:\*\* )(.*)$/u, assistant: false },
|
|
671
|
+
{ pattern: /^(- \*\*Usuário:\*\* )(.*)$/u, assistant: false },
|
|
672
|
+
{ pattern: /^(- \*\*Assistente:\*\* )(.*)$/u, assistant: true },
|
|
673
|
+
{ pattern: /^(- \*\*Resumo:\*\* )(.*)$/u, assistant: true },
|
|
674
|
+
{ pattern: /^(\*\*Estado ao final do turno:\*\* )(.*)$/u, assistant: true },
|
|
675
|
+
];
|
|
676
|
+
const GENERATED_CLOSING_LINE_RULES = [
|
|
677
|
+
{ pattern: /^(- \*\*Resumo final:\*\* )(.*)$/u, assistant: true },
|
|
678
|
+
];
|
|
679
|
+
|
|
680
|
+
function generatedSessionLine(line, rules) {
|
|
681
|
+
for (const rule of rules) {
|
|
682
|
+
const match = rule.pattern.exec(line);
|
|
683
|
+
if (match) return { ...rule, prefix: match[1], value: match[2] };
|
|
684
|
+
}
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function splitSessionMarkdownLines(source) {
|
|
689
|
+
const lines = [];
|
|
690
|
+
let cursor = 0;
|
|
691
|
+
while (cursor < source.length) {
|
|
692
|
+
const newline = source.indexOf('\n', cursor);
|
|
693
|
+
if (newline === -1) {
|
|
694
|
+
lines.push({ text: source.slice(cursor), eol: '' });
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
const textEnd = source[newline - 1] === '\r' ? newline - 1 : newline;
|
|
698
|
+
lines.push({ text: source.slice(cursor, textEnd), eol: source.slice(textEnd, newline + 1) });
|
|
699
|
+
cursor = newline + 1;
|
|
700
|
+
}
|
|
701
|
+
return lines;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function generatedMetadataContinuation(line, mode = '') {
|
|
705
|
+
const clean = line.trim();
|
|
706
|
+
if (!clean) return null;
|
|
707
|
+
if (/^<\/?session\s*>/i.test(clean)) return mode;
|
|
708
|
+
if (/^<\/?(?:oai-mem-citation|citation_entries|rollout_ids)\b/i.test(clean)) {
|
|
709
|
+
const nested = [...clean.matchAll(/<(citation_entries|rollout_ids)\b[^>]*>/gi)].at(-1);
|
|
710
|
+
return nested ? nested[1].toLowerCase() : mode;
|
|
711
|
+
}
|
|
712
|
+
if (mode === 'citation_entries' && (
|
|
713
|
+
/\|note=\[[^\]]*\]\s*$/i.test(clean)
|
|
714
|
+
|| /^[^\s<>]+:\d+(?:-\d+)?(?:\|[^\s].*)?$/i.test(clean)
|
|
715
|
+
)) return mode;
|
|
716
|
+
if (mode === 'rollout_ids' && /^(?:[0-9a-f]{8,}(?:-[0-9a-f-]+)*|019f-[A-Za-z0-9_-]+)$/i.test(clean)) {
|
|
717
|
+
return mode;
|
|
718
|
+
}
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
export function sanitizeGeneratedSessionMarkdown(content) {
|
|
723
|
+
const lines = splitSessionMarkdownLines(String(content || ''));
|
|
724
|
+
let section = '';
|
|
725
|
+
let output = '';
|
|
726
|
+
|
|
727
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
728
|
+
const line = lines[index];
|
|
729
|
+
if (/^## /u.test(line.text)) {
|
|
730
|
+
section = line.text === '## Iterações'
|
|
731
|
+
? 'iterations'
|
|
732
|
+
: (line.text === '## Encerramento' ? 'closing' : '');
|
|
733
|
+
output += `${line.text}${line.eol}`;
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const rules = section === 'iterations'
|
|
738
|
+
? GENERATED_ITERATION_LINE_RULES
|
|
739
|
+
: (section === 'closing' ? GENERATED_CLOSING_LINE_RULES : []);
|
|
740
|
+
const generated = generatedSessionLine(line.text, rules);
|
|
741
|
+
if (!generated) {
|
|
742
|
+
output += `${line.text}${line.eol}`;
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
let value = generated.value;
|
|
747
|
+
let last = index;
|
|
748
|
+
let mode = '';
|
|
749
|
+
if (generated.assistant) {
|
|
750
|
+
for (let next = index + 1; next < lines.length; next += 1) {
|
|
751
|
+
const nextMode = generatedMetadataContinuation(lines[next].text, mode);
|
|
752
|
+
if (nextMode === null) break;
|
|
753
|
+
value += `${lines[last].eol}${lines[next].text}`;
|
|
754
|
+
last = next;
|
|
755
|
+
mode = nextMode;
|
|
756
|
+
}
|
|
757
|
+
value = sanitizeAssistantMessage(value);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
output += `${generated.prefix}${escapeMarkdownHtmlTags(value)}${lines[last].eol}`;
|
|
761
|
+
index = last;
|
|
762
|
+
}
|
|
763
|
+
return output;
|
|
764
|
+
}
|
|
765
|
+
|
|
647
766
|
export function finalizeSessionFile(sessionPath, tx, created, endedAt, vaultBase = '') {
|
|
648
767
|
const pending = extractPending(tx.rawTextForDetection);
|
|
649
768
|
const links = (items) => items.length ? items.map((rel) => ` - ${wikilinkFromRel(rel)}`).join('\n') : ' - Nenhuma';
|
|
@@ -668,7 +787,7 @@ ${formatPendingClosing(pending)}
|
|
|
668
787
|
// As três seções derivadas saem do MESMO `created` que monta o Encerramento — antes
|
|
669
788
|
// elas ficavam de fora deste write e a nota mentia no corpo (ver hooks/derived-sections.mjs).
|
|
670
789
|
applyDerivedSections(
|
|
671
|
-
replacePendingSection(updateFrontmatter(content, endedAt), pending),
|
|
790
|
+
replacePendingSection(updateFrontmatter(sanitizeGeneratedSessionMarkdown(content), endedAt), pending),
|
|
672
791
|
created,
|
|
673
792
|
),
|
|
674
793
|
closing,
|
|
@@ -676,8 +795,9 @@ ${formatPendingClosing(pending)}
|
|
|
676
795
|
}
|
|
677
796
|
|
|
678
797
|
export function sessionFinalSummary(tx) {
|
|
679
|
-
|
|
680
|
-
|
|
798
|
+
const assistantSummary = sanitizeAssistantMessage(tx.latestAssistantMessage);
|
|
799
|
+
return assistantSummary
|
|
800
|
+
? compactText(assistantSummary, 500)
|
|
681
801
|
: `Sessão encerrada com ${tx.userPrompts.length} prompts e ${tx.tools.length} ferramentas registradas.`;
|
|
682
802
|
}
|
|
683
803
|
|
|
@@ -794,7 +914,206 @@ export function shouldAbortStopAfterStaging(causalStop, memoryAttempt) {
|
|
|
794
914
|
);
|
|
795
915
|
}
|
|
796
916
|
|
|
797
|
-
|
|
917
|
+
const STOP_OBSERVABILITY_DEADLINE_MS = 45_000;
|
|
918
|
+
|
|
919
|
+
function stopEntryCausalSnapshot(entry) {
|
|
920
|
+
const activationId = String(entry?.active_activation_id || '');
|
|
921
|
+
const activation = entry?.activations?.[activationId] || {};
|
|
922
|
+
return {
|
|
923
|
+
activationId,
|
|
924
|
+
activationEpoch: Number(activation.epoch || entry?.activation_epoch || 0),
|
|
925
|
+
turnSequence: Number(entry?.last_turn_sequence || activation.last_turn_sequence || 0),
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function expectedStopCausalSnapshot(entry, causalStop, turnSequence) {
|
|
930
|
+
if (!causalStop) return stopEntryCausalSnapshot(entry);
|
|
931
|
+
return {
|
|
932
|
+
activationId: String(causalStop.activationId || ''),
|
|
933
|
+
activationEpoch: Number(causalStop.activation?.epoch || entry?.activation_epoch || 0),
|
|
934
|
+
turnSequence: Number(turnSequence || 0),
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
function sameStopCausalSnapshot(left, right) {
|
|
939
|
+
return left.activationId === right.activationId
|
|
940
|
+
&& left.activationEpoch === right.activationEpoch
|
|
941
|
+
&& left.turnSequence === right.turnSequence;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function stopClaudeRoots(entry) {
|
|
945
|
+
const paths = new Set();
|
|
946
|
+
const add = (value) => {
|
|
947
|
+
if (typeof value === 'string' && value.trim()) paths.add(value.trim());
|
|
948
|
+
};
|
|
949
|
+
add(entry?.transcript_path);
|
|
950
|
+
for (const path of entry?.transcript_paths || []) add(path);
|
|
951
|
+
for (const activation of Object.values(entry?.activations || {})) {
|
|
952
|
+
add(activation?.transcript_path);
|
|
953
|
+
for (const path of activation?.transcript_paths || []) add(path);
|
|
954
|
+
}
|
|
955
|
+
return { state: 'complete', rootPaths: [...paths], descendantPaths: [], diagnostics: [] };
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function materializeStopObservability(request) {
|
|
959
|
+
return materializeSessionObservability({
|
|
960
|
+
vaultBase: request.vaultBase,
|
|
961
|
+
sessionPath: request.sessionPath,
|
|
962
|
+
transcriptPath: request.transcriptPath,
|
|
963
|
+
entry: request.entry,
|
|
964
|
+
canonicalConversationId: request.canonicalConversationId,
|
|
965
|
+
frontier: request.frontier,
|
|
966
|
+
signals: request.signals,
|
|
967
|
+
cache: request.cache,
|
|
968
|
+
mode: 'live',
|
|
969
|
+
deadlineAt: request.deadlineAt,
|
|
970
|
+
now: request.now,
|
|
971
|
+
allowNone: request.allowNone,
|
|
972
|
+
readRuntimeFrontier: request.readRuntimeFrontier,
|
|
973
|
+
withPublicationGuard: request.withPublicationGuard,
|
|
974
|
+
writeRegistryCheckpoint: request.writeRegistryCheckpoint,
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
export async function refreshStopObservability({
|
|
979
|
+
vaultBase,
|
|
980
|
+
input,
|
|
981
|
+
sessionPath,
|
|
982
|
+
sessionId,
|
|
983
|
+
entry,
|
|
984
|
+
causalStop,
|
|
985
|
+
turnSequence,
|
|
986
|
+
hookStartedAt,
|
|
987
|
+
expectedSignalSequence,
|
|
988
|
+
}, {
|
|
989
|
+
now = Date.now,
|
|
990
|
+
resolveEntry = resolveSessionEntry,
|
|
991
|
+
mutateRegistry = mutateSessionRegistry,
|
|
992
|
+
readStore = readObservabilityStore,
|
|
993
|
+
resolveRoots = resolveObservabilityRoots,
|
|
994
|
+
materialize = materializeStopObservability,
|
|
995
|
+
} = {}) {
|
|
996
|
+
if (causalStop && !causalStop.canPromoteMemory) return false;
|
|
997
|
+
const deadlineAt = hookStartedAt + STOP_OBSERVABILITY_DEADLINE_MS;
|
|
998
|
+
if (now() >= deadlineAt) return false;
|
|
999
|
+
|
|
1000
|
+
const expected = expectedStopCausalSnapshot(entry, causalStop, turnSequence);
|
|
1001
|
+
const fresh = resolveEntry(vaultBase, input, entry?.provider);
|
|
1002
|
+
if (fresh.identity?.state !== 'resolved'
|
|
1003
|
+
|| fresh.identity.canonicalConversationId !== sessionId
|
|
1004
|
+
|| !fresh.entry?.session_file
|
|
1005
|
+
|| fresh.entry.session_file !== entry?.session_file
|
|
1006
|
+
|| !sameStopCausalSnapshot(expected, stopEntryCausalSnapshot(fresh.entry))) return false;
|
|
1007
|
+
|
|
1008
|
+
const runtime = readStore(vaultBase, sessionId);
|
|
1009
|
+
const signalSequence = Number(runtime?.observability_signal_sequence || 0);
|
|
1010
|
+
if (expectedSignalSequence !== undefined
|
|
1011
|
+
&& signalSequence !== Number(expectedSignalSequence)) return false;
|
|
1012
|
+
if (now() >= deadlineAt) return false;
|
|
1013
|
+
|
|
1014
|
+
const roots = fresh.identity.provider === 'codex'
|
|
1015
|
+
? resolveRoots(fresh.entry)
|
|
1016
|
+
: stopClaudeRoots(fresh.entry);
|
|
1017
|
+
if (roots?.state !== 'complete' || !roots.rootPaths?.length) return false;
|
|
1018
|
+
|
|
1019
|
+
const frontier = {
|
|
1020
|
+
canonical_session_id: sessionId,
|
|
1021
|
+
activation_id: expected.activationId || 'legacy',
|
|
1022
|
+
activation_epoch: expected.activationEpoch,
|
|
1023
|
+
turn_sequence: expected.turnSequence,
|
|
1024
|
+
signal_sequence: signalSequence,
|
|
1025
|
+
roots_stat_hash: 'pending',
|
|
1026
|
+
graph_cursor: 'pending',
|
|
1027
|
+
source_manifest_hash: 'pending',
|
|
1028
|
+
};
|
|
1029
|
+
const readRuntimeFrontier = (candidateFrontier, guardContext) => {
|
|
1030
|
+
const currentEntry = guardContext?.entry;
|
|
1031
|
+
const current = currentEntry
|
|
1032
|
+
? { identity: { state: 'resolved', canonicalConversationId: sessionId }, entry: currentEntry }
|
|
1033
|
+
: resolveEntry(vaultBase, input, entry?.provider);
|
|
1034
|
+
if (current.identity?.state !== 'resolved'
|
|
1035
|
+
|| current.identity.canonicalConversationId !== sessionId
|
|
1036
|
+
|| !current.entry) {
|
|
1037
|
+
return { ...candidateFrontier, canonical_session_id: 'unresolved' };
|
|
1038
|
+
}
|
|
1039
|
+
const currentCausal = stopEntryCausalSnapshot(current.entry);
|
|
1040
|
+
const currentRuntime = readStore(vaultBase, sessionId);
|
|
1041
|
+
return {
|
|
1042
|
+
...candidateFrontier,
|
|
1043
|
+
activation_id: currentCausal.activationId || 'legacy',
|
|
1044
|
+
activation_epoch: currentCausal.activationEpoch,
|
|
1045
|
+
turn_sequence: currentCausal.turnSequence,
|
|
1046
|
+
signal_sequence: Math.max(
|
|
1047
|
+
Number(currentRuntime?.observability_signal_sequence || 0),
|
|
1048
|
+
Number(current.entry.observability_signal_sequence || 0),
|
|
1049
|
+
),
|
|
1050
|
+
};
|
|
1051
|
+
};
|
|
1052
|
+
const withPublicationGuard = (_candidateFrontier, publishGuarded) => (
|
|
1053
|
+
mutateRegistry(vaultBase, (registry) => publishGuarded({
|
|
1054
|
+
registry,
|
|
1055
|
+
entry: registry.sessions?.[sessionId] || null,
|
|
1056
|
+
}))
|
|
1057
|
+
);
|
|
1058
|
+
const writeRegistryCheckpoint = ({
|
|
1059
|
+
frontier: checkpointFrontier,
|
|
1060
|
+
state,
|
|
1061
|
+
diagnostics,
|
|
1062
|
+
snapshot,
|
|
1063
|
+
}, guardContext) => {
|
|
1064
|
+
const registry = guardContext?.registry;
|
|
1065
|
+
const current = registry?.sessions?.[sessionId];
|
|
1066
|
+
if (!current) return null;
|
|
1067
|
+
const currentSignal = Number(current.observability_signal_sequence || checkpointFrontier.signal_sequence);
|
|
1068
|
+
registry.sessions[sessionId] = {
|
|
1069
|
+
...current,
|
|
1070
|
+
observability_signal_sequence: currentSignal,
|
|
1071
|
+
observability_checkpoint_sequence: checkpointFrontier.signal_sequence,
|
|
1072
|
+
observability_dirty: currentSignal > checkpointFrontier.signal_sequence,
|
|
1073
|
+
observability_checkpoint_frontier: checkpointFrontier,
|
|
1074
|
+
subagents_observability_state: state,
|
|
1075
|
+
subagents_diagnostics: diagnostics || [],
|
|
1076
|
+
};
|
|
1077
|
+
return markObservabilityCheckpoint(vaultBase, sessionId, {
|
|
1078
|
+
checkpointSequence: checkpointFrontier.signal_sequence,
|
|
1079
|
+
frontier: checkpointFrontier,
|
|
1080
|
+
sourceManifest: snapshot?.subagents?.sourceManifest,
|
|
1081
|
+
graphCache: snapshot?.subagents?.cache,
|
|
1082
|
+
diagnostics,
|
|
1083
|
+
});
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
const result = await Promise.resolve(materialize({
|
|
1087
|
+
vaultBase,
|
|
1088
|
+
sessionPath,
|
|
1089
|
+
entry: fresh.entry,
|
|
1090
|
+
rootPaths: roots.rootPaths,
|
|
1091
|
+
transcriptPath: roots.rootPaths[0],
|
|
1092
|
+
caller: 'stop',
|
|
1093
|
+
canonicalConversationId: sessionId,
|
|
1094
|
+
activationId: expected.activationId,
|
|
1095
|
+
activationEpoch: expected.activationEpoch,
|
|
1096
|
+
turnSequence: expected.turnSequence,
|
|
1097
|
+
signalSequence,
|
|
1098
|
+
deadlineAt,
|
|
1099
|
+
allowNone: true,
|
|
1100
|
+
frontier,
|
|
1101
|
+
signals: runtime?.signals || [],
|
|
1102
|
+
cache: runtime?.graph_cache || null,
|
|
1103
|
+
now,
|
|
1104
|
+
readRuntimeFrontier,
|
|
1105
|
+
withPublicationGuard,
|
|
1106
|
+
writeRegistryCheckpoint,
|
|
1107
|
+
}));
|
|
1108
|
+
return !['stale', 'conflict', 'degraded', 'missing'].includes(result?.status);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
export async function main({
|
|
1112
|
+
stageMemory = stageStopMemoryAttempt,
|
|
1113
|
+
clock = Date.now,
|
|
1114
|
+
refreshObservability = refreshStopObservability,
|
|
1115
|
+
} = {}) {
|
|
1116
|
+
const hookStartedAt = clock();
|
|
798
1117
|
const input = readHookInput();
|
|
799
1118
|
if (input.stop_hook_active) {
|
|
800
1119
|
writeHookOutput({});
|
|
@@ -961,8 +1280,17 @@ export function main({ stageMemory = stageStopMemoryAttempt } = {}) {
|
|
|
961
1280
|
}
|
|
962
1281
|
|
|
963
1282
|
try {
|
|
964
|
-
|
|
965
|
-
vaultBase,
|
|
1283
|
+
await refreshObservability({
|
|
1284
|
+
vaultBase,
|
|
1285
|
+
input,
|
|
1286
|
+
sessionPath,
|
|
1287
|
+
sessionId,
|
|
1288
|
+
entry,
|
|
1289
|
+
causalStop,
|
|
1290
|
+
turnSequence: stopTurnSequence,
|
|
1291
|
+
hookStartedAt,
|
|
1292
|
+
}, {
|
|
1293
|
+
now: clock,
|
|
966
1294
|
});
|
|
967
1295
|
} catch (error) {
|
|
968
1296
|
process.stderr.write(`[wendkeep] Token usage falhou: ${error.message}\n`);
|
|
@@ -1062,7 +1390,7 @@ export function main({ stageMemory = stageStopMemoryAttempt } = {}) {
|
|
|
1062
1390
|
|
|
1063
1391
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
1064
1392
|
try {
|
|
1065
|
-
main();
|
|
1393
|
+
await main();
|
|
1066
1394
|
} catch (error) {
|
|
1067
1395
|
process.stderr.write(`[wendkeep] Stop falhou: ${error.message}\n`);
|
|
1068
1396
|
// Same reasoning as the identity bail: stderr is discarded by Codex. Exit stays 0 —
|