@hydraharness/harness-session-stats 0.1.1-rc.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/LICENSE +21 -0
- package/README.md +37 -0
- package/lib/index.js +196 -0
- package/lib/invariant.js +28 -0
- package/lib/types/client.d.ts +10 -0
- package/lib/types/client.js +10 -0
- package/lib/types/index.d.ts +23 -0
- package/lib/types/index.js +24 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/invariant.js +27 -0
- package/lib/types/projection.d.ts +129 -0
- package/lib/types/projection.js +166 -0
- package/lib/types/types.d.ts +42 -0
- package/lib/types/types.js +11 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @hydraharness/harness-session-stats
|
|
2
|
+
|
|
3
|
+
Function plugin registering the `sessionStats` projection unit: whole-log conversation figures — turn/step counts and the LLM, tool, first-token, and decode wall times — folded from step boundaries, stream chunks, tool pairs, and assembled assistant messages, and served through the session-projection seam (registry snapshot, change feed, and every projection carrier: history tail page, `session/projection` push frames, session list rows). Clients render full-session figures that paging and compaction cannot change; the reference consumer is the web chat stats strip, whose window fold mirrors these field names as its no-unit fallback.
|
|
4
|
+
|
|
5
|
+
## Fold semantics
|
|
6
|
+
|
|
7
|
+
- `steps` counts `step/end` events. The agent loop appends exactly one per entered step, in a `finally`, so completed, failed, cancelled, and max-tokens steps all count. Counting assembled assistant messages instead would overcount max-tokens usage-host messages (empty content, excluded from the surface) and undercount cancelled steps (aborted before the message assembles).
|
|
8
|
+
- `turns` counts distinct turns carrying at least one closed step; rejected or empty turns (closed with no step) are uncounted. Turn numbers are host-assigned and monotonic per session, so the fold keeps only the last counted turn.
|
|
9
|
+
- `llmMs` sums `step/start` → `assistant/message` per step that assembled a message (retry waits inside the step are model time, as in the window fold).
|
|
10
|
+
- `ttftMs`/`ttftSteps` sum and count `step/start` → first non-empty delta chunk; the first attempt's boundary survives an in-step `llm/retry` (window `resetForRetry` parity).
|
|
11
|
+
- `decodeMs`/`decodeTokens` sum first token → assembled message and the provider-reported output tokens, only over steps carrying both.
|
|
12
|
+
- `toolMs` sums `tool/call` → `tool/result` pairs matched by callId; unresolved calls are dropped at `turn/end` (results land within their turn).
|
|
13
|
+
- Every field is 0 until its first contributing event. A composed registry always serves the key, so clients read the value, never key presence.
|
|
14
|
+
|
|
15
|
+
## Composition
|
|
16
|
+
|
|
17
|
+
```yaml
|
|
18
|
+
- id: session-stats
|
|
19
|
+
name: '@hydraharness/harness-session-stats'
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Injects `sessionProjections` — the plugin's whole purpose; in assemblies without the registry the fiber stays pending and nothing registers.
|
|
23
|
+
|
|
24
|
+
## Model Experience
|
|
25
|
+
|
|
26
|
+
None, as the plugin only computes a client-facing read model of already-logged session events and touches no prompt, message, schema, stream, or tool result.
|
|
27
|
+
|
|
28
|
+
#### KV Cache effect
|
|
29
|
+
|
|
30
|
+
None; the plugin never assembles or sends provider requests.
|
|
31
|
+
|
|
32
|
+
## Known Limitations and Deferred Work
|
|
33
|
+
|
|
34
|
+
- **Steps count work attempted, not visible output** — a step that failed before producing any visible content still closed with `step/end` and counts; a step interrupted by a crash counts after the session reloads, when crash recovery appends its synthetic `step/end` (`interruptedTurnClosers` in @hydraharness/harness-session).
|
|
35
|
+
- **A cancelled step is counted but untimed** — no assistant message assembles, so its partial stream time enters no wall-time figure, matching the window fold's untimed interrupted node; a max-tokens usage-host message conversely contributes model time the surface does not show.
|
|
36
|
+
- **Counts are log-scoped, not surface-scoped** — steps whose messages were later compacted away stay counted; the figures describe the whole session, not the current model-visible surface.
|
|
37
|
+
- **Mounted only in the web-app bundle** — other assemblies serve no `sessionStats` key, and their consumers fall back to window-scoped counting (the web stats strip's fallback path).
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { isTokenDelta } from "@hydraharness/harness-llm/message";
|
|
3
|
+
//#region lib/types/projection.js
|
|
4
|
+
/**
|
|
5
|
+
* The `sessionStats` projection unit: a pure fold of step boundaries, stream
|
|
6
|
+
* chunks, tool pairs, and assembled assistant messages into whole-log counts
|
|
7
|
+
* and wall times.
|
|
8
|
+
*
|
|
9
|
+
* `step/end` — not `assistant/message` — is the counted step event because it
|
|
10
|
+
* is the step lifecycle authority: the loop appends exactly one per entered
|
|
11
|
+
* step, in a `finally`, so completed, failed, cancelled, and max-tokens steps
|
|
12
|
+
* all land one. Counting assembled assistant messages instead would overcount
|
|
13
|
+
* max-tokens usage-host messages (empty content, excluded from the surface)
|
|
14
|
+
* and undercount cancelled steps (aborted before the message assembles).
|
|
15
|
+
*
|
|
16
|
+
* The wall-time folds mirror the client window fold field by field
|
|
17
|
+
* (`deriveStats` in @hydraharness/harness-client-ui-conversation, that fold's whole-window
|
|
18
|
+
* fallback role): model time is `step/start` → `assistant/message`, first
|
|
19
|
+
* token is the first non-empty delta chunk and survives an in-step
|
|
20
|
+
* `llm/retry`, decode spans first token → assembled message on steps that
|
|
21
|
+
* also report output tokens, and tool time pairs `tool/call` → `tool/result`
|
|
22
|
+
* by callId. A cancelled step assembles no message, so its partial stream
|
|
23
|
+
* time stays uncounted in every time figure — matching the window, which
|
|
24
|
+
* renders it as an untimed interrupted node.
|
|
25
|
+
*
|
|
26
|
+
* @module @hydraharness/harness-session-stats/projection
|
|
27
|
+
*/
|
|
28
|
+
const sessionStatsSchema = z.object({
|
|
29
|
+
turns: z.number().int().nonnegative(),
|
|
30
|
+
steps: z.number().int().nonnegative(),
|
|
31
|
+
llmMs: z.number().nonnegative(),
|
|
32
|
+
toolMs: z.number().nonnegative(),
|
|
33
|
+
ttftMs: z.number().nonnegative(),
|
|
34
|
+
ttftSteps: z.number().int().nonnegative(),
|
|
35
|
+
decodeMs: z.number().nonnegative(),
|
|
36
|
+
decodeTokens: z.number().nonnegative()
|
|
37
|
+
}).strict();
|
|
38
|
+
/**
|
|
39
|
+
* The fold state's shape (totals plus in-flight boundaries), validated on
|
|
40
|
+
* persisted-cache rows after their `ver` gate — the unit's input boundary.
|
|
41
|
+
* The view is a strict subset of the state, so this schema extends
|
|
42
|
+
* `sessionStatsSchema` (the wire output boundary) with the boundary fields.
|
|
43
|
+
*/
|
|
44
|
+
const sessionStatsStateSchema = sessionStatsSchema.extend({
|
|
45
|
+
lastTurn: z.number().int().nonnegative().nullable(),
|
|
46
|
+
openStep: z.object({
|
|
47
|
+
turn: z.number().int().nonnegative(),
|
|
48
|
+
step: z.number().int().nonnegative(),
|
|
49
|
+
startTime: z.number().nonnegative(),
|
|
50
|
+
firstTokenTime: z.number().nonnegative().nullable()
|
|
51
|
+
}).nullable(),
|
|
52
|
+
pendingCalls: z.record(z.string(), z.number().nonnegative())
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* Provider-reported completion tokens, guarded the way the window fold guards
|
|
56
|
+
* node usage.
|
|
57
|
+
* @param usage - the assistant/message event's optional usage record.
|
|
58
|
+
* @returns the output-token count, or null when unreported or invalid.
|
|
59
|
+
*/
|
|
60
|
+
function usageOutputTokens(usage) {
|
|
61
|
+
if (typeof usage !== "object" || usage === null) return null;
|
|
62
|
+
const value = usage.outputTokens;
|
|
63
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
64
|
+
}
|
|
65
|
+
/** The `sessionStats` unit registered on `ctx.sessionProjections` (exported for the unit spec). */
|
|
66
|
+
const sessionStatsProjectionDefinition = {
|
|
67
|
+
key: "sessionStats",
|
|
68
|
+
stateVersion: 1,
|
|
69
|
+
stateSchema: sessionStatsStateSchema,
|
|
70
|
+
init: () => ({
|
|
71
|
+
turns: 0,
|
|
72
|
+
steps: 0,
|
|
73
|
+
llmMs: 0,
|
|
74
|
+
toolMs: 0,
|
|
75
|
+
ttftMs: 0,
|
|
76
|
+
ttftSteps: 0,
|
|
77
|
+
decodeMs: 0,
|
|
78
|
+
decodeTokens: 0,
|
|
79
|
+
lastTurn: null,
|
|
80
|
+
openStep: null,
|
|
81
|
+
pendingCalls: {}
|
|
82
|
+
}),
|
|
83
|
+
apply: (state, event) => {
|
|
84
|
+
switch (event.type) {
|
|
85
|
+
case "step/start": return {
|
|
86
|
+
...state,
|
|
87
|
+
openStep: {
|
|
88
|
+
turn: event.data.turn,
|
|
89
|
+
step: event.data.step,
|
|
90
|
+
startTime: event.time,
|
|
91
|
+
firstTokenTime: null
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
case "assistant/chunk": {
|
|
95
|
+
const open = state.openStep;
|
|
96
|
+
if (open === null || open.turn !== event.data.turn || open.step !== event.data.step) return state;
|
|
97
|
+
if (open.firstTokenTime !== null || !isTokenDelta(event.data.chunk)) return state;
|
|
98
|
+
return {
|
|
99
|
+
...state,
|
|
100
|
+
openStep: {
|
|
101
|
+
...open,
|
|
102
|
+
firstTokenTime: event.time
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
case "assistant/message": {
|
|
107
|
+
const open = state.openStep;
|
|
108
|
+
if (open === null || open.turn !== event.data.turn || open.step !== event.data.step) return state;
|
|
109
|
+
const next = {
|
|
110
|
+
...state,
|
|
111
|
+
llmMs: state.llmMs + Math.max(0, event.time - open.startTime),
|
|
112
|
+
openStep: null
|
|
113
|
+
};
|
|
114
|
+
if (open.firstTokenTime !== null) {
|
|
115
|
+
next.ttftMs += Math.max(0, open.firstTokenTime - open.startTime);
|
|
116
|
+
next.ttftSteps += 1;
|
|
117
|
+
const outputTokens = usageOutputTokens(event.data.usage);
|
|
118
|
+
if (outputTokens !== null) {
|
|
119
|
+
next.decodeMs += Math.max(0, event.time - open.firstTokenTime);
|
|
120
|
+
next.decodeTokens += outputTokens;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return next;
|
|
124
|
+
}
|
|
125
|
+
case "tool/call": return {
|
|
126
|
+
...state,
|
|
127
|
+
pendingCalls: {
|
|
128
|
+
...state.pendingCalls,
|
|
129
|
+
[event.data.callId]: event.time
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
case "tool/result": {
|
|
133
|
+
const callId = event.data.message.source.callId;
|
|
134
|
+
const dispatched = Object.hasOwn(state.pendingCalls, callId) ? state.pendingCalls[callId] : void 0;
|
|
135
|
+
if (dispatched === void 0) return state;
|
|
136
|
+
const pendingCalls = Object.fromEntries(Object.entries(state.pendingCalls).filter(([id]) => id !== callId));
|
|
137
|
+
return {
|
|
138
|
+
...state,
|
|
139
|
+
toolMs: state.toolMs + Math.max(0, event.time - dispatched),
|
|
140
|
+
pendingCalls
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
case "step/end": return {
|
|
144
|
+
...state,
|
|
145
|
+
turns: state.lastTurn === event.data.turn ? state.turns : state.turns + 1,
|
|
146
|
+
steps: state.steps + 1,
|
|
147
|
+
lastTurn: event.data.turn,
|
|
148
|
+
openStep: null
|
|
149
|
+
};
|
|
150
|
+
case "turn/end": return Object.keys(state.pendingCalls).length === 0 ? state : {
|
|
151
|
+
...state,
|
|
152
|
+
pendingCalls: {}
|
|
153
|
+
};
|
|
154
|
+
default: return state;
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
wire: {
|
|
158
|
+
viewSchema: sessionStatsSchema,
|
|
159
|
+
view: (state) => ({
|
|
160
|
+
turns: state.turns,
|
|
161
|
+
steps: state.steps,
|
|
162
|
+
llmMs: state.llmMs,
|
|
163
|
+
toolMs: state.toolMs,
|
|
164
|
+
ttftMs: state.ttftMs,
|
|
165
|
+
ttftSteps: state.ttftSteps,
|
|
166
|
+
decodeMs: state.decodeMs,
|
|
167
|
+
decodeTokens: state.decodeTokens
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region lib/types/index.js
|
|
173
|
+
/**
|
|
174
|
+
* Function plugin registering the `sessionStats` projection unit: whole-log
|
|
175
|
+
* turn/step counts and LLM/tool/first-token/decode wall times served through
|
|
176
|
+
* the session-projection seam (registry snapshot, change feed, and every
|
|
177
|
+
* projection carrier), so clients render full-session figures that paging and
|
|
178
|
+
* compaction cannot change. The plugin owns only the fold; delivery is the
|
|
179
|
+
* seam's.
|
|
180
|
+
*
|
|
181
|
+
* @module @hydraharness/harness-session-stats
|
|
182
|
+
*/
|
|
183
|
+
/** Cordis plugin name. */
|
|
184
|
+
const name = "session-stats";
|
|
185
|
+
/** The projection registry is the plugin's whole purpose; without it the fiber stays pending. */
|
|
186
|
+
const inject = ["sessionProjections"];
|
|
187
|
+
/**
|
|
188
|
+
* Register the `sessionStats` unit; the registration is an effect on this
|
|
189
|
+
* plugin's fiber, so unloading removes the key.
|
|
190
|
+
* @param ctx - registrant context carrying the projection registry.
|
|
191
|
+
*/
|
|
192
|
+
function apply(ctx) {
|
|
193
|
+
ctx.sessionProjections.register(sessionStatsProjectionDefinition);
|
|
194
|
+
}
|
|
195
|
+
//#endregion
|
|
196
|
+
export { apply, inject, name };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@hydraharness/harness-session-stats`.
|
|
4
|
+
* @module @hydraharness/harness-session-stats/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@hydraharness/harness-session-stats";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "session-stats-invariant";
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
const inject = ["invariants"];
|
|
11
|
+
/**
|
|
12
|
+
* No runtime invariant: the package owns a single pure projection fold whose
|
|
13
|
+
* wire payload is schema-validated by the projection registry at every
|
|
14
|
+
* snapshot and change-feed emission, and the event relations the fold relies
|
|
15
|
+
* on (`step/end` exactly once per entered step, monotonic host-assigned turn
|
|
16
|
+
* numbers, chunk and tool events carrying their step coordinates and call
|
|
17
|
+
* ids) are owned and runtime-checked by @hydraharness/harness-agent-loop and the session
|
|
18
|
+
* surface, not here.
|
|
19
|
+
*/
|
|
20
|
+
const install = () => {};
|
|
21
|
+
/**
|
|
22
|
+
* Register this package's invariant companion.
|
|
23
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
24
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
25
|
+
*/
|
|
26
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
27
|
+
//#endregion
|
|
28
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-namespace projection of the session-stats domain: a pure re-export
|
|
3
|
+
* of the package's types outlet. Client code imports ONLY the client
|
|
4
|
+
* namespace (repo discipline), so `./client` projects the same single-source
|
|
5
|
+
* content `./types` serves to host consumers — zero duplication.
|
|
6
|
+
*
|
|
7
|
+
* @module @hydraharness/harness-session-stats/client
|
|
8
|
+
*/
|
|
9
|
+
export type * from './types.ts';
|
|
10
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-namespace projection of the session-stats domain: a pure re-export
|
|
3
|
+
* of the package's types outlet. Client code imports ONLY the client
|
|
4
|
+
* namespace (repo discipline), so `./client` projects the same single-source
|
|
5
|
+
* content `./types` serves to host consumers — zero duplication.
|
|
6
|
+
*
|
|
7
|
+
* @module @hydraharness/harness-session-stats/client
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Function plugin registering the `sessionStats` projection unit: whole-log
|
|
3
|
+
* turn/step counts and LLM/tool/first-token/decode wall times served through
|
|
4
|
+
* the session-projection seam (registry snapshot, change feed, and every
|
|
5
|
+
* projection carrier), so clients render full-session figures that paging and
|
|
6
|
+
* compaction cannot change. The plugin owns only the fold; delivery is the
|
|
7
|
+
* seam's.
|
|
8
|
+
*
|
|
9
|
+
* @module @hydraharness/harness-session-stats
|
|
10
|
+
*/
|
|
11
|
+
import type { Context } from '@hydraharness/cordis';
|
|
12
|
+
export type * from './types.ts';
|
|
13
|
+
/** Cordis plugin name. */
|
|
14
|
+
export declare const name = "session-stats";
|
|
15
|
+
/** The projection registry is the plugin's whole purpose; without it the fiber stays pending. */
|
|
16
|
+
export declare const inject: string[];
|
|
17
|
+
/**
|
|
18
|
+
* Register the `sessionStats` unit; the registration is an effect on this
|
|
19
|
+
* plugin's fiber, so unloading removes the key.
|
|
20
|
+
* @param ctx - registrant context carrying the projection registry.
|
|
21
|
+
*/
|
|
22
|
+
export declare function apply(ctx: Context): void;
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Function plugin registering the `sessionStats` projection unit: whole-log
|
|
3
|
+
* turn/step counts and LLM/tool/first-token/decode wall times served through
|
|
4
|
+
* the session-projection seam (registry snapshot, change feed, and every
|
|
5
|
+
* projection carrier), so clients render full-session figures that paging and
|
|
6
|
+
* compaction cannot change. The plugin owns only the fold; delivery is the
|
|
7
|
+
* seam's.
|
|
8
|
+
*
|
|
9
|
+
* @module @hydraharness/harness-session-stats
|
|
10
|
+
*/
|
|
11
|
+
import { sessionStatsProjectionDefinition } from "./projection.js";
|
|
12
|
+
/** Cordis plugin name. */
|
|
13
|
+
export const name = 'session-stats';
|
|
14
|
+
/** The projection registry is the plugin's whole purpose; without it the fiber stays pending. */
|
|
15
|
+
export const inject = ['sessionProjections'];
|
|
16
|
+
/**
|
|
17
|
+
* Register the `sessionStats` unit; the registration is an effect on this
|
|
18
|
+
* plugin's fiber, so unloading removes the key.
|
|
19
|
+
* @param ctx - registrant context carrying the projection registry.
|
|
20
|
+
*/
|
|
21
|
+
export function apply(ctx) {
|
|
22
|
+
ctx.sessionProjections.register(sessionStatsProjectionDefinition);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@hydraharness/harness-session-stats`.
|
|
3
|
+
* @module @hydraharness/harness-session-stats/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@hydraharness/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "session-stats-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@hydraharness/harness-session-stats`.
|
|
3
|
+
* @module @hydraharness/harness-session-stats/invariant
|
|
4
|
+
*/
|
|
5
|
+
const PACKAGE_NAME = '@hydraharness/harness-session-stats';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export const name = 'session-stats-invariant';
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export const inject = ['invariants'];
|
|
10
|
+
/**
|
|
11
|
+
* No runtime invariant: the package owns a single pure projection fold whose
|
|
12
|
+
* wire payload is schema-validated by the projection registry at every
|
|
13
|
+
* snapshot and change-feed emission, and the event relations the fold relies
|
|
14
|
+
* on (`step/end` exactly once per entered step, monotonic host-assigned turn
|
|
15
|
+
* numbers, chunk and tool events carrying their step coordinates and call
|
|
16
|
+
* ids) are owned and runtime-checked by @hydraharness/harness-agent-loop and the session
|
|
17
|
+
* surface, not here.
|
|
18
|
+
*/
|
|
19
|
+
const install = () => { };
|
|
20
|
+
/**
|
|
21
|
+
* Register this package's invariant companion.
|
|
22
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
23
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
24
|
+
*/
|
|
25
|
+
export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
26
|
+
/* jscpd:ignore-end */
|
|
27
|
+
//# sourceMappingURL=invariant.js.map
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `sessionStats` projection unit: a pure fold of step boundaries, stream
|
|
3
|
+
* chunks, tool pairs, and assembled assistant messages into whole-log counts
|
|
4
|
+
* and wall times.
|
|
5
|
+
*
|
|
6
|
+
* `step/end` — not `assistant/message` — is the counted step event because it
|
|
7
|
+
* is the step lifecycle authority: the loop appends exactly one per entered
|
|
8
|
+
* step, in a `finally`, so completed, failed, cancelled, and max-tokens steps
|
|
9
|
+
* all land one. Counting assembled assistant messages instead would overcount
|
|
10
|
+
* max-tokens usage-host messages (empty content, excluded from the surface)
|
|
11
|
+
* and undercount cancelled steps (aborted before the message assembles).
|
|
12
|
+
*
|
|
13
|
+
* The wall-time folds mirror the client window fold field by field
|
|
14
|
+
* (`deriveStats` in @hydraharness/harness-client-ui-conversation, that fold's whole-window
|
|
15
|
+
* fallback role): model time is `step/start` → `assistant/message`, first
|
|
16
|
+
* token is the first non-empty delta chunk and survives an in-step
|
|
17
|
+
* `llm/retry`, decode spans first token → assembled message on steps that
|
|
18
|
+
* also report output tokens, and tool time pairs `tool/call` → `tool/result`
|
|
19
|
+
* by callId. A cancelled step assembles no message, so its partial stream
|
|
20
|
+
* time stays uncounted in every time figure — matching the window, which
|
|
21
|
+
* renders it as an untimed interrupted node.
|
|
22
|
+
*
|
|
23
|
+
* @module @hydraharness/harness-session-stats/projection
|
|
24
|
+
*/
|
|
25
|
+
import { z } from 'zod';
|
|
26
|
+
/** Accumulated whole-log figures (the view is exactly these totals). */
|
|
27
|
+
interface SessionStatsTotals {
|
|
28
|
+
/** Distinct turns with at least one closed step so far. */
|
|
29
|
+
turns: number;
|
|
30
|
+
/** Closed steps so far. */
|
|
31
|
+
steps: number;
|
|
32
|
+
/** Summed model wall time over message-assembling steps, ms. */
|
|
33
|
+
llmMs: number;
|
|
34
|
+
/** Summed matched tool call→result wall time, ms. */
|
|
35
|
+
toolMs: number;
|
|
36
|
+
/** Summed first-token latency over `ttftSteps`, ms. */
|
|
37
|
+
ttftMs: number;
|
|
38
|
+
/** Steps carrying a recorded first token. */
|
|
39
|
+
ttftSteps: number;
|
|
40
|
+
/** Summed decode wall time over usage-reporting steps, ms. */
|
|
41
|
+
decodeMs: number;
|
|
42
|
+
/** Summed provider output tokens over the same steps. */
|
|
43
|
+
decodeTokens: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Fold state: the totals plus the in-flight boundaries they accrue from.
|
|
47
|
+
* Turn numbers are host-assigned and monotonic per session, so a single
|
|
48
|
+
* `lastTurn` slot decides "first closed step of a new turn"; the state is
|
|
49
|
+
* plain JSON per the unit contract (persisted-cache precondition).
|
|
50
|
+
*/
|
|
51
|
+
interface SessionStatsState extends SessionStatsTotals {
|
|
52
|
+
/** Turn of the last counted `step/end`; null before the first. */
|
|
53
|
+
lastTurn: number | null;
|
|
54
|
+
/** The open step's boundary facts; null outside a step or after its message assembled. */
|
|
55
|
+
openStep: {
|
|
56
|
+
turn: number;
|
|
57
|
+
step: number;
|
|
58
|
+
startTime: number;
|
|
59
|
+
firstTokenTime: number | null;
|
|
60
|
+
} | null;
|
|
61
|
+
/** Dispatch times of tool calls whose result has not landed, by callId. */
|
|
62
|
+
pendingCalls: Record<string, number>;
|
|
63
|
+
}
|
|
64
|
+
declare module '@hydraharness/harness-session-projection/types' {
|
|
65
|
+
interface SessionProjectionStateMap {
|
|
66
|
+
sessionStats: SessionStatsState;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** The `sessionStats` unit registered on `ctx.sessionProjections` (exported for the unit spec). */
|
|
70
|
+
export declare const sessionStatsProjectionDefinition: {
|
|
71
|
+
key: "sessionStats";
|
|
72
|
+
stateVersion: number;
|
|
73
|
+
stateSchema: z.ZodObject<{
|
|
74
|
+
turns: z.ZodNumber;
|
|
75
|
+
steps: z.ZodNumber;
|
|
76
|
+
llmMs: z.ZodNumber;
|
|
77
|
+
toolMs: z.ZodNumber;
|
|
78
|
+
ttftMs: z.ZodNumber;
|
|
79
|
+
ttftSteps: z.ZodNumber;
|
|
80
|
+
decodeMs: z.ZodNumber;
|
|
81
|
+
decodeTokens: z.ZodNumber;
|
|
82
|
+
lastTurn: z.ZodNullable<z.ZodNumber>;
|
|
83
|
+
openStep: z.ZodNullable<z.ZodObject<{
|
|
84
|
+
turn: z.ZodNumber;
|
|
85
|
+
step: z.ZodNumber;
|
|
86
|
+
startTime: z.ZodNumber;
|
|
87
|
+
firstTokenTime: z.ZodNullable<z.ZodNumber>;
|
|
88
|
+
}, z.core.$strip>>;
|
|
89
|
+
pendingCalls: z.ZodRecord<z.ZodString, z.ZodNumber>;
|
|
90
|
+
}, z.core.$strict>;
|
|
91
|
+
init: () => {
|
|
92
|
+
turns: number;
|
|
93
|
+
steps: number;
|
|
94
|
+
llmMs: number;
|
|
95
|
+
toolMs: number;
|
|
96
|
+
ttftMs: number;
|
|
97
|
+
ttftSteps: number;
|
|
98
|
+
decodeMs: number;
|
|
99
|
+
decodeTokens: number;
|
|
100
|
+
lastTurn: null;
|
|
101
|
+
openStep: null;
|
|
102
|
+
pendingCalls: {};
|
|
103
|
+
};
|
|
104
|
+
apply: (state: NoInfer<SessionStatsState>, event: import("@hydraharness/harness-session").SessionEvent) => SessionStatsState;
|
|
105
|
+
wire: {
|
|
106
|
+
viewSchema: z.ZodObject<{
|
|
107
|
+
turns: z.ZodNumber;
|
|
108
|
+
steps: z.ZodNumber;
|
|
109
|
+
llmMs: z.ZodNumber;
|
|
110
|
+
toolMs: z.ZodNumber;
|
|
111
|
+
ttftMs: z.ZodNumber;
|
|
112
|
+
ttftSteps: z.ZodNumber;
|
|
113
|
+
decodeMs: z.ZodNumber;
|
|
114
|
+
decodeTokens: z.ZodNumber;
|
|
115
|
+
}, z.core.$strict>;
|
|
116
|
+
view: (state: NoInfer<SessionStatsState>) => {
|
|
117
|
+
turns: number;
|
|
118
|
+
steps: number;
|
|
119
|
+
llmMs: number;
|
|
120
|
+
toolMs: number;
|
|
121
|
+
ttftMs: number;
|
|
122
|
+
ttftSteps: number;
|
|
123
|
+
decodeMs: number;
|
|
124
|
+
decodeTokens: number;
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
export {};
|
|
129
|
+
//# sourceMappingURL=projection.d.ts.map
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `sessionStats` projection unit: a pure fold of step boundaries, stream
|
|
3
|
+
* chunks, tool pairs, and assembled assistant messages into whole-log counts
|
|
4
|
+
* and wall times.
|
|
5
|
+
*
|
|
6
|
+
* `step/end` — not `assistant/message` — is the counted step event because it
|
|
7
|
+
* is the step lifecycle authority: the loop appends exactly one per entered
|
|
8
|
+
* step, in a `finally`, so completed, failed, cancelled, and max-tokens steps
|
|
9
|
+
* all land one. Counting assembled assistant messages instead would overcount
|
|
10
|
+
* max-tokens usage-host messages (empty content, excluded from the surface)
|
|
11
|
+
* and undercount cancelled steps (aborted before the message assembles).
|
|
12
|
+
*
|
|
13
|
+
* The wall-time folds mirror the client window fold field by field
|
|
14
|
+
* (`deriveStats` in @hydraharness/harness-client-ui-conversation, that fold's whole-window
|
|
15
|
+
* fallback role): model time is `step/start` → `assistant/message`, first
|
|
16
|
+
* token is the first non-empty delta chunk and survives an in-step
|
|
17
|
+
* `llm/retry`, decode spans first token → assembled message on steps that
|
|
18
|
+
* also report output tokens, and tool time pairs `tool/call` → `tool/result`
|
|
19
|
+
* by callId. A cancelled step assembles no message, so its partial stream
|
|
20
|
+
* time stays uncounted in every time figure — matching the window, which
|
|
21
|
+
* renders it as an untimed interrupted node.
|
|
22
|
+
*
|
|
23
|
+
* @module @hydraharness/harness-session-stats/projection
|
|
24
|
+
*/
|
|
25
|
+
import { z } from 'zod';
|
|
26
|
+
import { isTokenDelta } from '@hydraharness/harness-llm/message';
|
|
27
|
+
const sessionStatsSchema = z.object({
|
|
28
|
+
turns: z.number().int().nonnegative(),
|
|
29
|
+
steps: z.number().int().nonnegative(),
|
|
30
|
+
llmMs: z.number().nonnegative(),
|
|
31
|
+
toolMs: z.number().nonnegative(),
|
|
32
|
+
ttftMs: z.number().nonnegative(),
|
|
33
|
+
ttftSteps: z.number().int().nonnegative(),
|
|
34
|
+
decodeMs: z.number().nonnegative(),
|
|
35
|
+
decodeTokens: z.number().nonnegative(),
|
|
36
|
+
}).strict();
|
|
37
|
+
/**
|
|
38
|
+
* The fold state's shape (totals plus in-flight boundaries), validated on
|
|
39
|
+
* persisted-cache rows after their `ver` gate — the unit's input boundary.
|
|
40
|
+
* The view is a strict subset of the state, so this schema extends
|
|
41
|
+
* `sessionStatsSchema` (the wire output boundary) with the boundary fields.
|
|
42
|
+
*/
|
|
43
|
+
const sessionStatsStateSchema = sessionStatsSchema.extend({
|
|
44
|
+
lastTurn: z.number().int().nonnegative().nullable(),
|
|
45
|
+
openStep: z.object({
|
|
46
|
+
turn: z.number().int().nonnegative(),
|
|
47
|
+
step: z.number().int().nonnegative(),
|
|
48
|
+
startTime: z.number().nonnegative(),
|
|
49
|
+
firstTokenTime: z.number().nonnegative().nullable(),
|
|
50
|
+
}).nullable(),
|
|
51
|
+
pendingCalls: z.record(z.string(), z.number().nonnegative()),
|
|
52
|
+
});
|
|
53
|
+
/**
|
|
54
|
+
* Provider-reported completion tokens, guarded the way the window fold guards
|
|
55
|
+
* node usage.
|
|
56
|
+
* @param usage - the assistant/message event's optional usage record.
|
|
57
|
+
* @returns the output-token count, or null when unreported or invalid.
|
|
58
|
+
*/
|
|
59
|
+
function usageOutputTokens(usage) {
|
|
60
|
+
if (typeof usage !== 'object' || usage === null)
|
|
61
|
+
return null;
|
|
62
|
+
const value = usage.outputTokens;
|
|
63
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : null;
|
|
64
|
+
}
|
|
65
|
+
/** The `sessionStats` unit registered on `ctx.sessionProjections` (exported for the unit spec). */
|
|
66
|
+
export const sessionStatsProjectionDefinition = {
|
|
67
|
+
key: 'sessionStats',
|
|
68
|
+
stateVersion: 1,
|
|
69
|
+
stateSchema: sessionStatsStateSchema,
|
|
70
|
+
init: () => ({
|
|
71
|
+
turns: 0,
|
|
72
|
+
steps: 0,
|
|
73
|
+
llmMs: 0,
|
|
74
|
+
toolMs: 0,
|
|
75
|
+
ttftMs: 0,
|
|
76
|
+
ttftSteps: 0,
|
|
77
|
+
decodeMs: 0,
|
|
78
|
+
decodeTokens: 0,
|
|
79
|
+
lastTurn: null,
|
|
80
|
+
openStep: null,
|
|
81
|
+
pendingCalls: {},
|
|
82
|
+
}),
|
|
83
|
+
apply: (state, event) => {
|
|
84
|
+
// Every uninteresting event returns the same reference (Object.is gates the change feed).
|
|
85
|
+
switch (event.type) {
|
|
86
|
+
case 'step/start':
|
|
87
|
+
return {
|
|
88
|
+
...state,
|
|
89
|
+
openStep: { turn: event.data.turn, step: event.data.step, startTime: event.time, firstTokenTime: null },
|
|
90
|
+
};
|
|
91
|
+
case 'assistant/chunk': {
|
|
92
|
+
const open = state.openStep;
|
|
93
|
+
if (open === null || open.turn !== event.data.turn || open.step !== event.data.step)
|
|
94
|
+
return state;
|
|
95
|
+
if (open.firstTokenTime !== null || !isTokenDelta(event.data.chunk))
|
|
96
|
+
return state;
|
|
97
|
+
return { ...state, openStep: { ...open, firstTokenTime: event.time } };
|
|
98
|
+
}
|
|
99
|
+
case 'assistant/message': {
|
|
100
|
+
const open = state.openStep;
|
|
101
|
+
if (open === null || open.turn !== event.data.turn || open.step !== event.data.step)
|
|
102
|
+
return state;
|
|
103
|
+
// One assembled message per step: closing the boundary means a
|
|
104
|
+
// defensive duplicate cannot accrue twice.
|
|
105
|
+
const next = {
|
|
106
|
+
...state,
|
|
107
|
+
llmMs: state.llmMs + Math.max(0, event.time - open.startTime),
|
|
108
|
+
openStep: null,
|
|
109
|
+
};
|
|
110
|
+
if (open.firstTokenTime !== null) {
|
|
111
|
+
next.ttftMs += Math.max(0, open.firstTokenTime - open.startTime);
|
|
112
|
+
next.ttftSteps += 1;
|
|
113
|
+
const outputTokens = usageOutputTokens(event.data.usage);
|
|
114
|
+
if (outputTokens !== null) {
|
|
115
|
+
next.decodeMs += Math.max(0, event.time - open.firstTokenTime);
|
|
116
|
+
next.decodeTokens += outputTokens;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return next;
|
|
120
|
+
}
|
|
121
|
+
case 'tool/call':
|
|
122
|
+
return { ...state, pendingCalls: { ...state.pendingCalls, [event.data.callId]: event.time } };
|
|
123
|
+
case 'tool/result': {
|
|
124
|
+
// Own-key check: callId is provider-minted (model/tool JSON boundary),
|
|
125
|
+
// so a prototype property name ('constructor', 'toString') on a result
|
|
126
|
+
// with no recorded call must read as unmatched, not as an inherited
|
|
127
|
+
// function that would poison toolMs with NaN.
|
|
128
|
+
const callId = event.data.message.source.callId;
|
|
129
|
+
const dispatched = Object.hasOwn(state.pendingCalls, callId) ? state.pendingCalls[callId] : undefined;
|
|
130
|
+
if (dispatched === undefined)
|
|
131
|
+
return state;
|
|
132
|
+
const pendingCalls = Object.fromEntries(Object.entries(state.pendingCalls).filter(([id]) => id !== callId));
|
|
133
|
+
return { ...state, toolMs: state.toolMs + Math.max(0, event.time - dispatched), pendingCalls };
|
|
134
|
+
}
|
|
135
|
+
case 'step/end':
|
|
136
|
+
return {
|
|
137
|
+
...state,
|
|
138
|
+
turns: state.lastTurn === event.data.turn ? state.turns : state.turns + 1,
|
|
139
|
+
steps: state.steps + 1,
|
|
140
|
+
lastTurn: event.data.turn,
|
|
141
|
+
openStep: null,
|
|
142
|
+
};
|
|
143
|
+
case 'turn/end':
|
|
144
|
+
// A call whose result never landed belongs to a cancelled or failed
|
|
145
|
+
// turn; results always land within their turn, so drop the leftovers
|
|
146
|
+
// instead of growing persisted state forever.
|
|
147
|
+
return Object.keys(state.pendingCalls).length === 0 ? state : { ...state, pendingCalls: {} };
|
|
148
|
+
default:
|
|
149
|
+
return state;
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
wire: {
|
|
153
|
+
viewSchema: sessionStatsSchema,
|
|
154
|
+
view: state => ({
|
|
155
|
+
turns: state.turns,
|
|
156
|
+
steps: state.steps,
|
|
157
|
+
llmMs: state.llmMs,
|
|
158
|
+
toolMs: state.toolMs,
|
|
159
|
+
ttftMs: state.ttftMs,
|
|
160
|
+
ttftSteps: state.ttftSteps,
|
|
161
|
+
decodeMs: state.decodeMs,
|
|
162
|
+
decodeTokens: state.decodeTokens,
|
|
163
|
+
}),
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
//# sourceMappingURL=projection.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure types of the session-stats domain: the ONE home of the `sessionStats`
|
|
3
|
+
* projection-key declaration, free of this package's host-side value imports
|
|
4
|
+
* (cordis context, zod, the llm chunk predicate). Two namespace projections
|
|
5
|
+
* serve it — `./types` for host consumers, `./client` for client aggregates —
|
|
6
|
+
* with zero content duplication.
|
|
7
|
+
*
|
|
8
|
+
* @module @hydraharness/harness-session-stats/types
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
/**
|
|
12
|
+
* Whole-log conversation figures, independent of how much history a client
|
|
13
|
+
* has paged in. Counts and wall times all fold from the complete durable log;
|
|
14
|
+
* every field is 0 until its first contributing event lands. Field names
|
|
15
|
+
* mirror the client window fold so an assembly without this unit can fall
|
|
16
|
+
* back to it wholesale.
|
|
17
|
+
*/
|
|
18
|
+
export interface SessionStatsProjection {
|
|
19
|
+
/** Distinct turns carrying at least one closed step (`step/end`); rejected or empty turns are uncounted. */
|
|
20
|
+
turns: number;
|
|
21
|
+
/** Closed steps (`step/end` events) — completed, failed, and cancelled steps alike. */
|
|
22
|
+
steps: number;
|
|
23
|
+
/** Summed model wall time (`step/start` → `assistant/message`) over steps that assembled a message. */
|
|
24
|
+
llmMs: number;
|
|
25
|
+
/** Summed tool wall time over `tool/call` → `tool/result` pairs matched by callId. */
|
|
26
|
+
toolMs: number;
|
|
27
|
+
/** Summed first-token latency (`step/start` → first non-empty delta chunk) over `ttftSteps`. */
|
|
28
|
+
ttftMs: number;
|
|
29
|
+
/** Steps carrying a recorded first token. */
|
|
30
|
+
ttftSteps: number;
|
|
31
|
+
/** Summed decode wall time (first token → `assistant/message`) over steps that also report output tokens. */
|
|
32
|
+
decodeMs: number;
|
|
33
|
+
/** Summed provider output tokens over the same decode-timed steps. */
|
|
34
|
+
decodeTokens: number;
|
|
35
|
+
}
|
|
36
|
+
declare module '@hydraharness/harness-session-projection/types' {
|
|
37
|
+
interface SessionProjectionMap {
|
|
38
|
+
/** Whole-log turn/step counts and wall times; see {@link SessionStatsProjection}. */
|
|
39
|
+
sessionStats: SessionStatsProjection;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure types of the session-stats domain: the ONE home of the `sessionStats`
|
|
3
|
+
* projection-key declaration, free of this package's host-side value imports
|
|
4
|
+
* (cordis context, zod, the llm chunk predicate). Two namespace projections
|
|
5
|
+
* serve it — `./types` for host consumers, `./client` for client aggregates —
|
|
6
|
+
* with zero content duplication.
|
|
7
|
+
*
|
|
8
|
+
* @module @hydraharness/harness-session-stats/types
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=types.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hydraharness/harness-session-stats",
|
|
3
|
+
"description": "Whole-log conversation counts and wall times projection (sessionStats) for the Hydra harness",
|
|
4
|
+
"hydra": {
|
|
5
|
+
"plugin": {
|
|
6
|
+
"application": "Show conversation counts and elapsed times derived from the session history."
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"version": "0.1.1-rc.6",
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/MaiHongPhong1902/Hydra-Harness.git",
|
|
16
|
+
"directory": "packages/session/session-stats"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "lib/index.js",
|
|
20
|
+
"types": "lib/types/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./lib/types/index.d.ts",
|
|
24
|
+
"default": "./lib/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./invariant": {
|
|
27
|
+
"types": "./lib/types/invariant.d.ts",
|
|
28
|
+
"default": "./lib/invariant.js"
|
|
29
|
+
},
|
|
30
|
+
"./types": {
|
|
31
|
+
"types": "./lib/types/types.d.ts",
|
|
32
|
+
"default": "./lib/types/types.js"
|
|
33
|
+
},
|
|
34
|
+
"./client": {
|
|
35
|
+
"types": "./lib/types/client.d.ts",
|
|
36
|
+
"default": "./lib/types/client.js"
|
|
37
|
+
},
|
|
38
|
+
"./src/*": "./src/*",
|
|
39
|
+
"./package.json": "./package.json"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"lib/index.js",
|
|
43
|
+
"lib/invariant.js",
|
|
44
|
+
"lib/types/**/*.js",
|
|
45
|
+
"lib/types/**/*.d.ts"
|
|
46
|
+
],
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"@hydraharness/harness-invariants": "^0.1.1-rc.6",
|
|
50
|
+
"@hydraharness/harness-llm": "^0.1.1-rc.6",
|
|
51
|
+
"@hydraharness/harness-session": "^0.1.1-rc.6",
|
|
52
|
+
"@hydraharness/harness-session-projection": "^0.1.1-rc.6",
|
|
53
|
+
"@hydraharness/cordis": "^4.0.2"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"zod": "^4.4.3"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@hydraharness/cordis-plugin-loader": "^1.0.3",
|
|
60
|
+
"@hydraharness/cordis-plugin-include": "^1.0.7",
|
|
61
|
+
"@hydraharness/harness-invariants": "^0.1.1-rc.6",
|
|
62
|
+
"@hydraharness/harness-session": "^0.1.1-rc.6",
|
|
63
|
+
"@hydraharness/harness-llm": "^0.1.1-rc.6",
|
|
64
|
+
"@hydraharness/harness-session-projection": "^0.1.1-rc.6",
|
|
65
|
+
"@hydraharness/cordis": "^4.0.2"
|
|
66
|
+
}
|
|
67
|
+
}
|