@bjoernrochel/sevro 0.1.0-rc.19 → 0.1.0-rc.20
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/docs/installing.md +2 -2
- package/docs/native-hosts.md +67 -0
- package/package.json +1 -1
- package/src/hosts/codex.ts +9 -5
- package/src/hosts/skill-hooks.ts +185 -47
package/docs/installing.md
CHANGED
|
@@ -23,10 +23,10 @@ Local installation records the selected version in the project lockfile.
|
|
|
23
23
|
|
|
24
24
|
## Current release
|
|
25
25
|
|
|
26
|
-
The current release is `0.1.0-rc.
|
|
26
|
+
The current release is `0.1.0-rc.20`. Install this exact version:
|
|
27
27
|
|
|
28
28
|
```sh
|
|
29
|
-
bun add --exact @bjoernrochel/sevro@0.1.0-rc.
|
|
29
|
+
bun add --exact @bjoernrochel/sevro@0.1.0-rc.20
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
<!-- sevro-current-release:end -->
|
package/docs/native-hosts.md
CHANGED
|
@@ -90,6 +90,73 @@ Newly created or changed skill bodies are outside the prepared snapshot. A
|
|
|
90
90
|
complete empty observation is limited to this scope and the supported local
|
|
91
91
|
tool hook paths.
|
|
92
92
|
|
|
93
|
+
Codex observations include `incompleteReasons`: an empty array for a complete
|
|
94
|
+
collection, or all applicable reason codes for a partial collection. Older
|
|
95
|
+
observations without this field have unknown reasons; their missing details
|
|
96
|
+
cannot be reconstructed from balanced native call/return counts.
|
|
97
|
+
|
|
98
|
+
| Reason | Meaning |
|
|
99
|
+
| ------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
|
|
100
|
+
| `host_incomplete` | Host execution did not complete. |
|
|
101
|
+
| `catalog_incomplete` | The prepared catalog could not be collected completely. |
|
|
102
|
+
| `receipts_unavailable` | The receipt file is missing or unreadable. |
|
|
103
|
+
| `invalid_receipts` | A malformed receipt stopped processing; earlier valid receipts remain available. |
|
|
104
|
+
| `receipt_limit` | The receipt file exceeds the collection byte limit. |
|
|
105
|
+
| `observer_failure` | The hook recorded a failure or writer limit, including input, nesting or matching-budget failures. |
|
|
106
|
+
| `session_identity_missing` | No root session identity was available. |
|
|
107
|
+
| `session_start_missing` / `session_stop_missing` | The root session lacks the corresponding lifecycle hook. |
|
|
108
|
+
| `pending_tool_calls` | Pre-tool hooks remain without matching post-tool hooks. |
|
|
109
|
+
| `unmatched_post_tool_use` | Post-tool hooks were received without pending pre-tool hooks. |
|
|
110
|
+
| `duplicate_tool_calls` | A pre-tool identity was already pending or finished. |
|
|
111
|
+
| `partial_hook_events` | A hook receipt is partial independently of body ambiguity. |
|
|
112
|
+
| `ambiguous_skill_bodies` | Complete body exposure could not distinguish skill names. |
|
|
113
|
+
| `partial_skill_bodies` | Recognized fragments remain without complete body exposure. |
|
|
114
|
+
| `calls_truncated` | Relevant call evidence exceeded its public count or byte budget. |
|
|
115
|
+
|
|
116
|
+
The `collection` summary reports `eventCount`, `preToolUseCount`,
|
|
117
|
+
`postToolUseCount`, `pendingToolCallCount`, `unmatchedPostToolUseCount`,
|
|
118
|
+
`duplicateToolCallCount` and `partialEventCount`, plus `sessionIdentified`,
|
|
119
|
+
`sessionStarted` and `sessionStopped` for the root session. Counts cover all
|
|
120
|
+
successfully parsed receipts, including receipts omitted from `calls`; an
|
|
121
|
+
unreadable or invalid tail remains unknown. Tool pairing spans the observed
|
|
122
|
+
sessions and uses session and tool-use identities. A later balanced pair is
|
|
123
|
+
still counted after an earlier anomaly.
|
|
124
|
+
|
|
125
|
+
`calls` is a bounded selection of complete matches, fragment-only matches,
|
|
126
|
+
ambiguous or partial receipts, and receipts associated with pending, orphaned
|
|
127
|
+
or duplicate tool identities. Each entry's `fragmentSkills` names recognizable
|
|
128
|
+
fragments without exposing their body offsets or digests. A fragment entry can
|
|
129
|
+
remain in `calls` after later complete exposure resolves `partialSkills`.
|
|
130
|
+
Neither `partial: false` on an individual receipt nor an empty `partialSkills`
|
|
131
|
+
array proves aggregate completeness. `truncated` describes this selected call
|
|
132
|
+
list, which is never the complete observer event ledger. A pending tool hook
|
|
133
|
+
can follow a rejected launch or a missing hook; it does not establish either
|
|
134
|
+
cause, and a tool error with a balanced hook pair does not itself imply an
|
|
135
|
+
observer gap. These diagnostics preserve unknown activation verdicts and do
|
|
136
|
+
not recover activation from native transcripts.
|
|
137
|
+
|
|
138
|
+
Codex also reports `primarySkill` and `primarySkillCompleteness` separately
|
|
139
|
+
from whole-collection completeness. A complete collection establishes its first
|
|
140
|
+
observed skill when the first matching receipt credits exactly one skill, or
|
|
141
|
+
`null` when none was observed. Multiple bodies credited by the first receipt
|
|
142
|
+
leave primary selection unknown even with complete collection; their catalog
|
|
143
|
+
order does not prove selection order. A partial collection can still
|
|
144
|
+
establish the primary skill when its only gap is pending tool calls, exactly one
|
|
145
|
+
skill was credited by the first matching receipt, and every call started through
|
|
146
|
+
that receipt has a matching post hook. A later pending call cannot change that
|
|
147
|
+
established first selection. An earlier or overlapping pending call leaves the
|
|
148
|
+
primary unknown (`null` with `primarySkillCompleteness: partial`). Other
|
|
149
|
+
collection gaps remain conservative. Consumers may use the established primary
|
|
150
|
+
for first-selection assertions while retaining the partial collection status;
|
|
151
|
+
this claim does not establish exclusions or a complete supporting sequence.
|
|
152
|
+
When a partial collection establishes the primary, the same collector emits
|
|
153
|
+
`sevro.host.skill-activation-claims` as complete evidence with scope
|
|
154
|
+
`primary_and_positive_presence`, the qualified `primarySkill`, and `presentSkills`.
|
|
155
|
+
It retains `collectionCompleteness: partial`. Consumers cite this receipt for
|
|
156
|
+
those specific claims, preserving the protocol's requirement that passing
|
|
157
|
+
results cite complete evidence. It cannot establish an exclusion or an
|
|
158
|
+
unobserved supporting read and is absent when the primary is unknown.
|
|
159
|
+
|
|
93
160
|
Claude's `skill_tool_result` method observes its existing structured `Skill`
|
|
94
161
|
stream without enabling additional command hooks. It pairs each call with a
|
|
95
162
|
successful result under the same session, parent actor and tool-use ID.
|
package/package.json
CHANGED
package/src/hosts/codex.ts
CHANGED
|
@@ -44,6 +44,7 @@ import { codexSkillCatalogRoots } from "./skill-hook-catalog";
|
|
|
44
44
|
import {
|
|
45
45
|
prepareSkillTracking,
|
|
46
46
|
skillTrackingObservation,
|
|
47
|
+
skillActivationClaims,
|
|
47
48
|
type SkillTracking,
|
|
48
49
|
} from "./skill-hooks";
|
|
49
50
|
import {
|
|
@@ -139,6 +140,7 @@ function codexCapabilities(
|
|
|
139
140
|
"sevro.host.native-transcripts",
|
|
140
141
|
"sevro.host.hooks",
|
|
141
142
|
"sevro.host.skill-activations",
|
|
143
|
+
"sevro.host.skill-activation-claims",
|
|
142
144
|
...(entrypoint === "app-server" ? ["sevro.host.native-goal"] : []),
|
|
143
145
|
"sevro.codex.plugin-marketplace",
|
|
144
146
|
"sevro.codex.explicit-invocation",
|
|
@@ -732,6 +734,11 @@ function appServerObservation(result: AppServerResult | null): Observation[] {
|
|
|
732
734
|
: [];
|
|
733
735
|
}
|
|
734
736
|
async function codexObservations(context: ExecutionContext, turns: TurnResult) {
|
|
737
|
+
const tracking = await skillTrackingObservation(
|
|
738
|
+
context.skillTracking,
|
|
739
|
+
turns.summary.threadId,
|
|
740
|
+
turns.summary.complete,
|
|
741
|
+
);
|
|
735
742
|
const native = await codexNativeCallObservation(
|
|
736
743
|
context.state.codexHome,
|
|
737
744
|
turns.summary.threadId,
|
|
@@ -752,11 +759,8 @@ async function codexObservations(context: ExecutionContext, turns: TurnResult) {
|
|
|
752
759
|
codexNativeControls(native),
|
|
753
760
|
...(turns.continuation ? [turns.continuation] : []),
|
|
754
761
|
...(explicit ? [explicit] : []),
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
turns.summary.threadId,
|
|
758
|
-
turns.summary.complete,
|
|
759
|
-
),
|
|
762
|
+
tracking,
|
|
763
|
+
...skillActivationClaims(tracking),
|
|
760
764
|
];
|
|
761
765
|
}
|
|
762
766
|
|
package/src/hosts/skill-hooks.ts
CHANGED
|
@@ -142,69 +142,107 @@ function receiptLabels(value: Record<string, unknown>): boolean {
|
|
|
142
142
|
|
|
143
143
|
async function receipts(tracking: SkillTracking) {
|
|
144
144
|
const root = tracking.configuration.receipts;
|
|
145
|
-
const result = { events: [] as SkillHookReceipt[],
|
|
145
|
+
const result = { events: [] as SkillHookReceipt[], reasons: [] as string[] };
|
|
146
|
+
if (await Bun.file(join(root, "partial")).exists())
|
|
147
|
+
result.reasons.push("observer_failure");
|
|
146
148
|
try {
|
|
147
149
|
const path = join(root, "events.jsonl");
|
|
148
150
|
if ((await stat(path)).size > MAX_EVENTS_BYTES)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
151
|
+
return { ...result, reasons: [...result.reasons, "receipt_limit"] };
|
|
152
|
+
parseReceipts(
|
|
153
|
+
await readFile(path, "utf8"),
|
|
154
|
+
tracking.configuration.skills,
|
|
155
|
+
result.events,
|
|
156
|
+
);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
result.reasons.push(
|
|
159
|
+
error instanceof SyntaxError
|
|
160
|
+
? "invalid_receipts"
|
|
161
|
+
: "receipts_unavailable",
|
|
162
|
+
);
|
|
159
163
|
}
|
|
160
164
|
return result;
|
|
161
165
|
}
|
|
162
166
|
|
|
167
|
+
function parseReceipts(
|
|
168
|
+
text: string,
|
|
169
|
+
skills: HookSkill[],
|
|
170
|
+
events: SkillHookReceipt[],
|
|
171
|
+
) {
|
|
172
|
+
for (const line of text.trimEnd().split("\n")) {
|
|
173
|
+
const event: unknown = JSON.parse(line);
|
|
174
|
+
if (!isReceipt(event, skills))
|
|
175
|
+
throw new SyntaxError("invalid skill hook receipt");
|
|
176
|
+
events.push(event);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
163
180
|
function hookKey(event: SkillHookReceipt): string {
|
|
164
181
|
return JSON.stringify([event.sessionId, event.toolUseId]);
|
|
165
182
|
}
|
|
166
183
|
|
|
167
|
-
function
|
|
184
|
+
function collectionSummary(
|
|
168
185
|
events: SkillHookReceipt[],
|
|
169
186
|
sessionId: string | null,
|
|
170
|
-
)
|
|
171
|
-
if (!lifecycleAvailable(events, sessionId)) return false;
|
|
187
|
+
) {
|
|
172
188
|
const ledger = {
|
|
173
189
|
pending: new Set<string>(),
|
|
174
190
|
finished: new Set<string>(),
|
|
175
|
-
|
|
191
|
+
diagnosticKeys: new Set<string>(),
|
|
192
|
+
duplicateToolCallCount: 0,
|
|
193
|
+
unmatchedPostToolUseCount: 0,
|
|
176
194
|
};
|
|
177
195
|
for (const event of events) updatePending(event, ledger);
|
|
178
|
-
return
|
|
179
|
-
ledger.pending
|
|
180
|
-
|
|
181
|
-
events.
|
|
182
|
-
|
|
196
|
+
return {
|
|
197
|
+
diagnosticKeys: new Set([...ledger.diagnosticKeys, ...ledger.pending]),
|
|
198
|
+
eventCount: events.length,
|
|
199
|
+
preToolUseCount: events.filter((event) => event.event === "PreToolUse")
|
|
200
|
+
.length,
|
|
201
|
+
postToolUseCount: events.filter((event) => event.event === "PostToolUse")
|
|
202
|
+
.length,
|
|
203
|
+
pendingToolCallCount: ledger.pending.size,
|
|
204
|
+
duplicateToolCallCount: ledger.duplicateToolCallCount,
|
|
205
|
+
unmatchedPostToolUseCount: ledger.unmatchedPostToolUseCount,
|
|
206
|
+
partialEventCount: events.filter((event) => event.partial).length,
|
|
207
|
+
...lifecycleSummary(events, sessionId),
|
|
208
|
+
};
|
|
183
209
|
}
|
|
184
210
|
|
|
185
|
-
function
|
|
211
|
+
function lifecycleSummary(
|
|
186
212
|
events: SkillHookReceipt[],
|
|
187
213
|
sessionId: string | null,
|
|
188
|
-
)
|
|
214
|
+
) {
|
|
189
215
|
const selected = events.filter((event) => event.sessionId === sessionId);
|
|
190
|
-
return
|
|
191
|
-
sessionId !== null
|
|
192
|
-
selected.some((event) => event.event === "SessionStart")
|
|
193
|
-
selected.some((event) => event.event === "Stop")
|
|
194
|
-
|
|
216
|
+
return {
|
|
217
|
+
sessionIdentified: sessionId !== null,
|
|
218
|
+
sessionStarted: selected.some((event) => event.event === "SessionStart"),
|
|
219
|
+
sessionStopped: selected.some((event) => event.event === "Stop"),
|
|
220
|
+
};
|
|
195
221
|
}
|
|
196
222
|
|
|
197
223
|
function updatePending(
|
|
198
224
|
event: SkillHookReceipt,
|
|
199
|
-
ledger: {
|
|
225
|
+
ledger: {
|
|
226
|
+
pending: Set<string>;
|
|
227
|
+
finished: Set<string>;
|
|
228
|
+
diagnosticKeys: Set<string>;
|
|
229
|
+
duplicateToolCallCount: number;
|
|
230
|
+
unmatchedPostToolUseCount: number;
|
|
231
|
+
},
|
|
200
232
|
) {
|
|
201
233
|
const key = hookKey(event);
|
|
202
234
|
if (event.event === "PreToolUse") {
|
|
203
|
-
|
|
235
|
+
if (duplicateCall(key, ledger)) {
|
|
236
|
+
ledger.duplicateToolCallCount++;
|
|
237
|
+
ledger.diagnosticKeys.add(key);
|
|
238
|
+
}
|
|
204
239
|
ledger.pending.add(key);
|
|
205
240
|
}
|
|
206
241
|
if (event.event === "PostToolUse") {
|
|
207
|
-
|
|
242
|
+
if (!ledger.pending.delete(key)) {
|
|
243
|
+
ledger.unmatchedPostToolUseCount++;
|
|
244
|
+
ledger.diagnosticKeys.add(key);
|
|
245
|
+
}
|
|
208
246
|
ledger.finished.add(key);
|
|
209
247
|
}
|
|
210
248
|
}
|
|
@@ -299,7 +337,12 @@ function pagedEvents(events: SkillHookReceipt[], skills: HookSkill[]) {
|
|
|
299
337
|
function publicReceipt(event: SkillHookReceipt) {
|
|
300
338
|
const receipt = { ...event };
|
|
301
339
|
delete receipt.bodyRanges;
|
|
302
|
-
return
|
|
340
|
+
return {
|
|
341
|
+
...receipt,
|
|
342
|
+
fragmentSkills: [
|
|
343
|
+
...new Set((event.bodyRanges ?? []).map((page) => page.invocation)),
|
|
344
|
+
],
|
|
345
|
+
};
|
|
303
346
|
}
|
|
304
347
|
|
|
305
348
|
function incompleteSkills(events: SkillHookReceipt[]) {
|
|
@@ -317,10 +360,15 @@ function incompleteSkills(events: SkillHookReceipt[]) {
|
|
|
317
360
|
return [...fragments].filter((invocation) => !settled.has(invocation));
|
|
318
361
|
}
|
|
319
362
|
|
|
320
|
-
function retainedCalls(
|
|
321
|
-
|
|
363
|
+
function retainedCalls(
|
|
364
|
+
events: SkillHookReceipt[],
|
|
365
|
+
diagnosticKeys: Set<string>,
|
|
366
|
+
) {
|
|
367
|
+
const calls: ReturnType<typeof publicReceipt>[] = [];
|
|
322
368
|
let truncated = false;
|
|
323
|
-
for (const event of events.filter((event) =>
|
|
369
|
+
for (const event of events.filter((event) =>
|
|
370
|
+
relevantCall(event, diagnosticKeys),
|
|
371
|
+
)) {
|
|
324
372
|
calls.push(publicReceipt(event));
|
|
325
373
|
if (
|
|
326
374
|
calls.length > MAX_RETAINED_CALLS ||
|
|
@@ -334,24 +382,113 @@ function retainedCalls(events: SkillHookReceipt[]) {
|
|
|
334
382
|
return { calls, truncated };
|
|
335
383
|
}
|
|
336
384
|
|
|
385
|
+
function relevantCall(
|
|
386
|
+
event: SkillHookReceipt,
|
|
387
|
+
diagnosticKeys: Set<string>,
|
|
388
|
+
): boolean {
|
|
389
|
+
return [
|
|
390
|
+
event.invocations.length > 0,
|
|
391
|
+
event.ambiguousInvocations.length > 0,
|
|
392
|
+
(event.bodyRanges ?? []).length > 0,
|
|
393
|
+
event.partial,
|
|
394
|
+
diagnosticKeys.has(hookKey(event)),
|
|
395
|
+
].some(Boolean);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function primaryEvidence(events: SkillHookReceipt[], reasons: string[]) {
|
|
399
|
+
const index = events.findIndex((event) => event.invocations.length > 0);
|
|
400
|
+
const first = events[index];
|
|
401
|
+
const complete = primarySelectionComplete(events, index, reasons);
|
|
402
|
+
if (!complete)
|
|
403
|
+
return { primarySkill: null, primarySkillCompleteness: "partial" as const };
|
|
404
|
+
return {
|
|
405
|
+
primarySkill: first?.invocations[0] ?? null,
|
|
406
|
+
primarySkillCompleteness: "complete" as const,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function primarySelectionComplete(
|
|
411
|
+
events: SkillHookReceipt[],
|
|
412
|
+
index: number,
|
|
413
|
+
reasons: string[],
|
|
414
|
+
) {
|
|
415
|
+
const matches = events[index]?.invocations.length ?? 0;
|
|
416
|
+
if (matches > 1) return false;
|
|
417
|
+
if (reasons.length === 0) return true;
|
|
418
|
+
return primaryBeforeGap(events, index, reasons);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function primaryBeforeGap(
|
|
422
|
+
events: SkillHookReceipt[],
|
|
423
|
+
index: number,
|
|
424
|
+
reasons: string[],
|
|
425
|
+
) {
|
|
426
|
+
if (reasons.join(",") !== "pending_tool_calls") return false;
|
|
427
|
+
if (events[index]?.invocations.length !== 1) return false;
|
|
428
|
+
const prefix = collectionSummary(events.slice(0, index + 1), null);
|
|
429
|
+
return prefix.pendingToolCallCount === 0;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function skillActivationClaims(
|
|
433
|
+
observation: Awaited<ReturnType<typeof skillTrackingObservation>>,
|
|
434
|
+
) {
|
|
435
|
+
if (observation.completeness !== "partial") return [];
|
|
436
|
+
if (observation.data.primarySkillCompleteness !== "complete") return [];
|
|
437
|
+
return [
|
|
438
|
+
{
|
|
439
|
+
id: "sevro.host.skill-activation-claims",
|
|
440
|
+
completeness: "complete" as const,
|
|
441
|
+
data: {
|
|
442
|
+
method: "tool_result_body_match",
|
|
443
|
+
scope: "primary_and_positive_presence",
|
|
444
|
+
collectionCompleteness: "partial",
|
|
445
|
+
primarySkill: observation.data.primarySkill,
|
|
446
|
+
presentSkills: observation.data.observedSkills,
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
];
|
|
450
|
+
}
|
|
451
|
+
|
|
337
452
|
export async function skillTrackingObservation(
|
|
338
453
|
tracking: SkillTracking,
|
|
339
454
|
sessionId: string | null,
|
|
340
455
|
completed: boolean,
|
|
341
456
|
) {
|
|
342
457
|
const collected = await receipts(tracking);
|
|
343
|
-
const { partial } = collected;
|
|
344
458
|
const events = pagedEvents(collected.events, tracking.configuration.skills);
|
|
345
459
|
const partialSkills = incompleteSkills(events);
|
|
346
|
-
const {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
]
|
|
460
|
+
const { diagnosticKeys, ...collection } = collectionSummary(
|
|
461
|
+
events,
|
|
462
|
+
sessionId,
|
|
463
|
+
);
|
|
464
|
+
const { calls, truncated } = retainedCalls(events, diagnosticKeys);
|
|
465
|
+
const ambiguousSkills = [
|
|
466
|
+
...new Set(events.flatMap((event) => event.ambiguousInvocations)),
|
|
467
|
+
];
|
|
468
|
+
const conditions: [string, boolean][] = [
|
|
469
|
+
["host_incomplete", !completed],
|
|
470
|
+
["catalog_incomplete", !tracking.configuration.catalogComplete],
|
|
471
|
+
["session_identity_missing", !collection.sessionIdentified],
|
|
472
|
+
["session_start_missing", !collection.sessionStarted],
|
|
473
|
+
["session_stop_missing", !collection.sessionStopped],
|
|
474
|
+
["pending_tool_calls", collection.pendingToolCallCount > 0],
|
|
475
|
+
["unmatched_post_tool_use", collection.unmatchedPostToolUseCount > 0],
|
|
476
|
+
["duplicate_tool_calls", collection.duplicateToolCallCount > 0],
|
|
477
|
+
[
|
|
478
|
+
"partial_hook_events",
|
|
479
|
+
events.some(
|
|
480
|
+
(event) => event.partial && event.ambiguousInvocations.length === 0,
|
|
481
|
+
),
|
|
482
|
+
],
|
|
483
|
+
["ambiguous_skill_bodies", ambiguousSkills.length > 0],
|
|
484
|
+
["partial_skill_bodies", partialSkills.length > 0],
|
|
485
|
+
["calls_truncated", truncated],
|
|
486
|
+
];
|
|
487
|
+
const incompleteReasons = [
|
|
488
|
+
...collected.reasons,
|
|
489
|
+
...conditions.filter(([, applies]) => applies).map(([reason]) => reason),
|
|
490
|
+
];
|
|
491
|
+
const complete = incompleteReasons.length === 0;
|
|
355
492
|
return {
|
|
356
493
|
id: "sevro.host.skill-activations",
|
|
357
494
|
completeness: complete ? ("complete" as const) : ("partial" as const),
|
|
@@ -361,12 +498,13 @@ export async function skillTrackingObservation(
|
|
|
361
498
|
scope: "prepared_skill_bodies",
|
|
362
499
|
catalogComplete: tracking.configuration.catalogComplete,
|
|
363
500
|
observedSkills: [...new Set(calls.flatMap((call) => call.invocations))],
|
|
501
|
+
...primaryEvidence(events, incompleteReasons),
|
|
364
502
|
partialSkills,
|
|
365
|
-
ambiguousSkills
|
|
366
|
-
...new Set(events.flatMap((event) => event.ambiguousInvocations)),
|
|
367
|
-
],
|
|
503
|
+
ambiguousSkills,
|
|
368
504
|
calls,
|
|
369
505
|
truncated,
|
|
506
|
+
incompleteReasons,
|
|
507
|
+
collection,
|
|
370
508
|
},
|
|
371
509
|
};
|
|
372
510
|
}
|