@stigmer/server 3.25.0 → 3.26.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/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +11 -8
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/list-indexes.d.ts +3 -0
- package/dist/boot/list-indexes.d.ts.map +1 -0
- package/dist/boot/list-indexes.js +24 -0
- package/dist/boot/list-indexes.js.map +1 -0
- package/dist/domain/agentexecution/controller.d.ts.map +1 -1
- package/dist/domain/agentexecution/controller.js +6 -8
- package/dist/domain/agentexecution/controller.js.map +1 -1
- package/dist/domain/agentexecution/list-index.d.ts +2 -0
- package/dist/domain/agentexecution/list-index.d.ts.map +1 -0
- package/dist/domain/agentexecution/list-index.js +23 -0
- package/dist/domain/agentexecution/list-index.js.map +1 -0
- package/dist/domain/agentexecution/steps.d.ts +17 -33
- package/dist/domain/agentexecution/steps.d.ts.map +1 -1
- package/dist/domain/agentexecution/steps.js +94 -95
- package/dist/domain/agentexecution/steps.js.map +1 -1
- package/dist/domain/agentexecution/usage.d.ts.map +1 -1
- package/dist/domain/agentexecution/usage.js +37 -14
- package/dist/domain/agentexecution/usage.js.map +1 -1
- package/dist/domain/artifact/controller.d.ts.map +1 -1
- package/dist/domain/artifact/controller.js +27 -15
- package/dist/domain/artifact/controller.js.map +1 -1
- package/dist/domain/artifact/list-index.d.ts +2 -0
- package/dist/domain/artifact/list-index.d.ts.map +1 -0
- package/dist/domain/artifact/list-index.js +21 -0
- package/dist/domain/artifact/list-index.js.map +1 -0
- package/dist/domain/session/list-index.d.ts +4 -0
- package/dist/domain/session/list-index.d.ts.map +1 -0
- package/dist/domain/session/list-index.js +26 -0
- package/dist/domain/session/list-index.js.map +1 -0
- package/dist/domain/session/steps.d.ts +7 -8
- package/dist/domain/session/steps.d.ts.map +1 -1
- package/dist/domain/session/steps.js +76 -78
- package/dist/domain/session/steps.js.map +1 -1
- package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
- package/dist/domain/workflowexecution/controller.js +53 -55
- package/dist/domain/workflowexecution/controller.js.map +1 -1
- package/dist/domain/workflowexecution/execution-filter.d.ts +4 -0
- package/dist/domain/workflowexecution/execution-filter.d.ts.map +1 -1
- package/dist/domain/workflowexecution/execution-filter.js +11 -5
- package/dist/domain/workflowexecution/execution-filter.js.map +1 -1
- package/dist/domain/workflowexecution/get-execution-summary.d.ts.map +1 -1
- package/dist/domain/workflowexecution/get-execution-summary.js +23 -17
- package/dist/domain/workflowexecution/get-execution-summary.js.map +1 -1
- package/dist/domain/workflowexecution/list-index.d.ts +2 -0
- package/dist/domain/workflowexecution/list-index.d.ts.map +1 -0
- package/dist/domain/workflowexecution/list-index.js +21 -0
- package/dist/domain/workflowexecution/list-index.js.map +1 -0
- package/dist/domain/workflowexecution/list-pending-approvals.d.ts.map +1 -1
- package/dist/domain/workflowexecution/list-pending-approvals.js +69 -32
- package/dist/domain/workflowexecution/list-pending-approvals.js.map +1 -1
- package/dist/domain/workflowexecution/queries.d.ts +30 -4
- package/dist/domain/workflowexecution/queries.d.ts.map +1 -1
- package/dist/domain/workflowexecution/queries.js +76 -22
- package/dist/domain/workflowexecution/queries.js.map +1 -1
- package/dist/extensions/list-read-scope.d.ts +9 -12
- package/dist/extensions/list-read-scope.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pipeline/steps/list-page.d.ts +63 -0
- package/dist/pipeline/steps/list-page.d.ts.map +1 -0
- package/dist/pipeline/steps/list-page.js +187 -0
- package/dist/pipeline/steps/list-page.js.map +1 -0
- package/dist/query/activity/handler.d.ts +6 -7
- package/dist/query/activity/handler.d.ts.map +1 -1
- package/dist/query/activity/handler.js +42 -59
- package/dist/query/activity/handler.js.map +1 -1
- package/dist/store/interface.d.ts +29 -0
- package/dist/store/interface.d.ts.map +1 -1
- package/dist/store/interface.js.map +1 -1
- package/dist/store/list-index.d.ts +123 -0
- package/dist/store/list-index.d.ts.map +1 -0
- package/dist/store/list-index.js +277 -0
- package/dist/store/list-index.js.map +1 -0
- package/dist/store/logger.d.ts +3 -0
- package/dist/store/logger.d.ts.map +1 -1
- package/dist/store/logger.js +5 -1
- package/dist/store/logger.js.map +1 -1
- package/dist/store/postgres/migrations.d.ts +10 -6
- package/dist/store/postgres/migrations.d.ts.map +1 -1
- package/dist/store/postgres/migrations.js +64 -6
- package/dist/store/postgres/migrations.js.map +1 -1
- package/dist/store/postgres/store.d.ts +26 -5
- package/dist/store/postgres/store.d.ts.map +1 -1
- package/dist/store/postgres/store.js +377 -22
- package/dist/store/postgres/store.js.map +1 -1
- package/dist/store/sqlite/migrations.d.ts +11 -5
- package/dist/store/sqlite/migrations.d.ts.map +1 -1
- package/dist/store/sqlite/migrations.js +50 -4
- package/dist/store/sqlite/migrations.js.map +1 -1
- package/dist/store/sqlite/store.d.ts +18 -3
- package/dist/store/sqlite/store.d.ts.map +1 -1
- package/dist/store/sqlite/store.js +321 -15
- package/dist/store/sqlite/store.js.map +1 -1
- package/package.json +6 -6
- package/src/authorization/__tests__/drivers.ts +6 -1
- package/src/boot/__tests__/list-indexes.test.ts +65 -0
- package/src/boot/compose.ts +23 -13
- package/src/boot/list-indexes.ts +25 -0
- package/src/domain/agentexecution/__tests__/agentexecution.test.ts +85 -3
- package/src/domain/agentexecution/controller.ts +8 -10
- package/src/domain/agentexecution/list-index.ts +24 -0
- package/src/domain/agentexecution/steps.ts +118 -115
- package/src/domain/agentexecution/usage.ts +38 -17
- package/src/domain/artifact/controller.ts +28 -18
- package/src/domain/artifact/list-index.ts +22 -0
- package/src/domain/session/list-index.ts +28 -0
- package/src/domain/session/steps.ts +108 -123
- package/src/domain/workflowexecution/__tests__/list-read-scope-summaries.test.ts +155 -99
- package/src/domain/workflowexecution/__tests__/workflowexecution.test.ts +193 -31
- package/src/domain/workflowexecution/controller.ts +54 -82
- package/src/domain/workflowexecution/execution-filter.ts +20 -7
- package/src/domain/workflowexecution/get-execution-summary.ts +31 -24
- package/src/domain/workflowexecution/list-index.ts +22 -0
- package/src/domain/workflowexecution/list-pending-approvals.ts +82 -25
- package/src/domain/workflowexecution/queries.ts +134 -23
- package/src/extensions/list-read-scope.ts +9 -12
- package/src/index.ts +10 -1
- package/src/pipeline/steps/__tests__/list-page.test.ts +186 -0
- package/src/pipeline/steps/list-page.ts +284 -0
- package/src/query/activity/handler.ts +57 -78
- package/src/store/README.md +51 -32
- package/src/store/__tests__/list-index.measure.test.ts +293 -0
- package/src/store/__tests__/list-index.test.ts +328 -0
- package/src/store/__tests__/store-contract.ts +346 -3
- package/src/store/interface.ts +39 -0
- package/src/store/list-index.ts +465 -0
- package/src/store/logger.ts +8 -1
- package/src/store/postgres/__tests__/list-index-repair.test.ts +133 -0
- package/src/store/postgres/__tests__/migrations.test.ts +27 -19
- package/src/store/postgres/__tests__/store-contract.test.ts +50 -3
- package/src/store/postgres/migrations.ts +69 -7
- package/src/store/postgres/store.ts +485 -29
- package/src/store/sqlite/__tests__/migrations.test.ts +51 -7
- package/src/store/sqlite/__tests__/store-contract.test.ts +70 -16
- package/src/store/sqlite/__tests__/support.ts +12 -3
- package/src/store/sqlite/migrations.ts +57 -6
- package/src/store/sqlite/store.ts +462 -21
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
* list_by_session.go, plus the full-scan load helper the usage reports
|
|
4
4
|
* share.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* Both lists read through the list index (list-index.ts beside this file;
|
|
7
|
+
* the store's contract in store/interface.ts), newest first by spec-audit
|
|
8
|
+
* created_at. `list` pages (pipeline/steps/list-page.ts): the store
|
|
9
|
+
* narrows by the request's org, the phase filter and then the read scope
|
|
10
|
+
* run on each batch — the scope last, so a composed driver is asked about
|
|
11
|
+
* the rows the request keeps. `listBySession` returns the session's
|
|
12
|
+
* executions whole: a conversation is read as one, and its consumers (the
|
|
13
|
+
* transcript, the channel and guest turn limits) need all of it.
|
|
10
14
|
*/
|
|
11
15
|
import { create, fromBinary } from "@bufbuild/protobuf";
|
|
12
16
|
|
|
@@ -22,12 +26,19 @@ import type { ListReadScope } from "../../extensions/list-read-scope.js";
|
|
|
22
26
|
import { restrictListByReadScope } from "../../extensions/list-read-scope.js";
|
|
23
27
|
import { internalError, invalidArgumentError } from "../../pipeline/errors.js";
|
|
24
28
|
import type { PipelineStep } from "../../pipeline/pipeline.js";
|
|
29
|
+
import {
|
|
30
|
+
listPageFingerprint,
|
|
31
|
+
readListPage,
|
|
32
|
+
} from "../../pipeline/steps/list-page.js";
|
|
33
|
+
import type { ListPage } from "../../pipeline/steps/list-page.js";
|
|
25
34
|
import type { Store } from "../../store/interface.js";
|
|
35
|
+
import type { ListIndexRow } from "../../store/list-index.js";
|
|
36
|
+
import { agentExecutionListIndex } from "./list-index.js";
|
|
26
37
|
|
|
27
38
|
/**
|
|
28
|
-
* Inter-step key for the working
|
|
29
|
-
*
|
|
30
|
-
* the
|
|
39
|
+
* Inter-step key for the working page AND the final AgentExecutionList —
|
|
40
|
+
* Go reuses one key ("execution_list") for both, and the controller reads
|
|
41
|
+
* the final response from it.
|
|
31
42
|
*/
|
|
32
43
|
export const EXECUTION_LIST_KEY = "execution_list";
|
|
33
44
|
|
|
@@ -36,8 +47,9 @@ type ListBySessionDesc =
|
|
|
36
47
|
typeof AgentExecutionQueryController.method.listBySession.input;
|
|
37
48
|
|
|
38
49
|
/**
|
|
39
|
-
* Full-scan agent-execution load
|
|
40
|
-
*
|
|
50
|
+
* Full-scan agent-execution load for the usage reports, whose org match
|
|
51
|
+
* is case-insensitive (Go strings.EqualFold) where the list index's is
|
|
52
|
+
* exact; malformed rows warn + skip (Go's proto.Unmarshal-continue).
|
|
41
53
|
*/
|
|
42
54
|
export async function loadAllAgentExecutions(
|
|
43
55
|
store: Store,
|
|
@@ -49,21 +61,7 @@ export async function loadAllAgentExecutions(
|
|
|
49
61
|
} catch (error) {
|
|
50
62
|
throw internalError(error, "failed to list agent executions");
|
|
51
63
|
}
|
|
52
|
-
|
|
53
|
-
const executions: AgentExecution[] = [];
|
|
54
|
-
for (const data of rows) {
|
|
55
|
-
let execution: AgentExecution;
|
|
56
|
-
try {
|
|
57
|
-
execution = fromBinary(AgentExecutionSchema, data);
|
|
58
|
-
} catch (error) {
|
|
59
|
-
logger.warn("Failed to unmarshal execution, skipping", {
|
|
60
|
-
error: error instanceof Error ? error.message : String(error),
|
|
61
|
-
});
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
executions.push(execution);
|
|
65
|
-
}
|
|
66
|
-
return executions;
|
|
64
|
+
return decodeExecutions(rows, logger);
|
|
67
65
|
}
|
|
68
66
|
|
|
69
67
|
/** ValidateListRequest — list.go: no required fields today (a no-op). */
|
|
@@ -71,92 +69,54 @@ export function newValidateListRequestStep(): PipelineStep<ListDesc> {
|
|
|
71
69
|
return {
|
|
72
70
|
name: "ValidateListRequest",
|
|
73
71
|
execute() {
|
|
74
|
-
// Go validates nothing here yet;
|
|
75
|
-
//
|
|
72
|
+
// Go validates nothing here yet; page_size is protovalidate's and
|
|
73
|
+
// page_token the page reader's.
|
|
76
74
|
},
|
|
77
75
|
};
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* org and phase, not the kind (extensions/list-read-scope.ts, the
|
|
86
|
-
* header's last contract line).
|
|
79
|
+
* QueryExecutionPage — one page of the request's executions: its org
|
|
80
|
+
* through the index when it names one, its phase when it names one, the
|
|
81
|
+
* read scope last (census lane 4; with no scope composed, every matching
|
|
82
|
+
* execution — the OSS single-user posture).
|
|
87
83
|
*/
|
|
88
|
-
export function
|
|
84
|
+
export function newQueryExecutionPageStep(
|
|
89
85
|
store: Store,
|
|
90
86
|
logger: Logger,
|
|
91
|
-
): PipelineStep<ListDesc> {
|
|
92
|
-
return {
|
|
93
|
-
name: "QueryAllExecutions",
|
|
94
|
-
async execute(ctx) {
|
|
95
|
-
const executions = await loadAllAgentExecutions(store, logger);
|
|
96
|
-
logger.debug("Successfully queried executions", {
|
|
97
|
-
count: executions.length,
|
|
98
|
-
});
|
|
99
|
-
ctx.set(EXECUTION_LIST_KEY, executions);
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* RestrictByReadScope — the list chain's last per-row predicate (census
|
|
106
|
-
* lane 4). With a composed ListReadScope (20260830.01) the working list
|
|
107
|
-
* narrows to the caller's authorized executions ∩ the request's org (the
|
|
108
|
-
* Java AgentExecutionListHandler baseline: org set = one-org view, blank
|
|
109
|
-
* = permission-bounded across orgs; the guest cookie rule rides the
|
|
110
|
-
* driver); no scope = the working list unchanged, org a no-op —
|
|
111
|
-
* byte-identical. Placed after ApplyPhaseFilter so the driver is asked
|
|
112
|
-
* about the rows the request keeps, never about the whole kind.
|
|
113
|
-
*/
|
|
114
|
-
export function newRestrictByReadScopeStep(
|
|
115
87
|
listReadScope: ListReadScope | undefined,
|
|
116
88
|
): PipelineStep<ListDesc> {
|
|
117
89
|
return {
|
|
118
|
-
name: "
|
|
90
|
+
name: "QueryExecutionPage",
|
|
119
91
|
async execute(ctx) {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* ApplyPhaseFilter — list.go: status.phase equality filter, skipped when
|
|
137
|
-
* the request carries no phase.
|
|
138
|
-
*/
|
|
139
|
-
export function newApplyPhaseFilterStep(
|
|
140
|
-
logger: Logger,
|
|
141
|
-
): PipelineStep<ListDesc> {
|
|
142
|
-
return {
|
|
143
|
-
name: "ApplyPhaseFilter",
|
|
144
|
-
execute(ctx) {
|
|
145
|
-
const executions = requireExecutions(ctx.get(EXECUTION_LIST_KEY));
|
|
146
|
-
if (ctx.input.phase === ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED) {
|
|
147
|
-
logger.debug("No phase filter specified, skipping");
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
const filtered = executions.filter(
|
|
151
|
-
(execution) =>
|
|
92
|
+
const input = ctx.input;
|
|
93
|
+
const page = await readListPage({
|
|
94
|
+
store,
|
|
95
|
+
declaration: agentExecutionListIndex,
|
|
96
|
+
query: { org: input.org },
|
|
97
|
+
request: input,
|
|
98
|
+
fingerprint: listPageFingerprint({
|
|
99
|
+
lane: "agentExecution.list",
|
|
100
|
+
org: input.org,
|
|
101
|
+
phase: input.phase,
|
|
102
|
+
tags: input.tags,
|
|
103
|
+
}),
|
|
104
|
+
decode: (data) => decodeExecution(data, logger),
|
|
105
|
+
keep: (execution) =>
|
|
106
|
+
input.phase === ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED ||
|
|
152
107
|
(execution.status?.phase ??
|
|
153
|
-
ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED) ===
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
108
|
+
ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED) === input.phase,
|
|
109
|
+
scope: (executions) =>
|
|
110
|
+
restrictListByReadScope(
|
|
111
|
+
listReadScope,
|
|
112
|
+
ctx.callerIdentity,
|
|
113
|
+
ApiResourceKind.agent_execution,
|
|
114
|
+
executions,
|
|
115
|
+
"",
|
|
116
|
+
),
|
|
117
|
+
failure: "failed to list agent executions",
|
|
158
118
|
});
|
|
159
|
-
ctx.set(EXECUTION_LIST_KEY,
|
|
119
|
+
ctx.set(EXECUTION_LIST_KEY, page);
|
|
160
120
|
},
|
|
161
121
|
};
|
|
162
122
|
}
|
|
@@ -176,9 +136,9 @@ export function newValidateListBySessionRequestStep(): PipelineStep<ListBySessio
|
|
|
176
136
|
}
|
|
177
137
|
|
|
178
138
|
/**
|
|
179
|
-
* QueryExecutionsBySession — list_by_session.go:
|
|
180
|
-
*
|
|
181
|
-
*
|
|
139
|
+
* QueryExecutionsBySession — list_by_session.go: the session's executions
|
|
140
|
+
* through the index's session key, whole and newest first, then the read
|
|
141
|
+
* scope (census lane 5; bounded by the session id, org never consulted).
|
|
182
142
|
*/
|
|
183
143
|
export function newQueryExecutionsBySessionStep(
|
|
184
144
|
store: Store,
|
|
@@ -188,31 +148,41 @@ export function newQueryExecutionsBySessionStep(
|
|
|
188
148
|
return {
|
|
189
149
|
name: "QueryExecutionsBySession",
|
|
190
150
|
async execute(ctx) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
)
|
|
151
|
+
let rows: ListIndexRow[];
|
|
152
|
+
try {
|
|
153
|
+
rows = await store.queryResources(agentExecutionListIndex, {
|
|
154
|
+
anyKey: [{ name: "session", value: ctx.input.sessionId }],
|
|
155
|
+
});
|
|
156
|
+
} catch (error) {
|
|
157
|
+
throw internalError(error, "failed to list agent executions");
|
|
158
|
+
}
|
|
197
159
|
const executions = await restrictListByReadScope(
|
|
198
160
|
listReadScope,
|
|
199
161
|
ctx.callerIdentity,
|
|
200
162
|
ApiResourceKind.agent_execution,
|
|
201
|
-
|
|
163
|
+
decodeExecutions(
|
|
164
|
+
rows.map((row) => row.data),
|
|
165
|
+
logger,
|
|
166
|
+
),
|
|
202
167
|
"",
|
|
203
168
|
);
|
|
204
169
|
logger.debug("Successfully queried executions by session", {
|
|
205
170
|
sessionId: ctx.input.sessionId,
|
|
206
171
|
count: executions.length,
|
|
207
172
|
});
|
|
208
|
-
|
|
173
|
+
const page: ListPage<AgentExecution> = {
|
|
174
|
+
entries: executions,
|
|
175
|
+
nextPageToken: "",
|
|
176
|
+
};
|
|
177
|
+
ctx.set(EXECUTION_LIST_KEY, page);
|
|
209
178
|
},
|
|
210
179
|
};
|
|
211
180
|
}
|
|
212
181
|
|
|
213
182
|
/**
|
|
214
|
-
* BuildExecutionListResponse — both list files: wraps the
|
|
215
|
-
*
|
|
183
|
+
* BuildExecutionListResponse — both list files: wraps the page into
|
|
184
|
+
* AgentExecutionList; total_pages is 1 when the response is whole and 0
|
|
185
|
+
* when a token follows (the contract's "not computed").
|
|
216
186
|
*/
|
|
217
187
|
export function newBuildExecutionListResponseStep<
|
|
218
188
|
Desc extends ListDesc | ListBySessionDesc,
|
|
@@ -220,24 +190,57 @@ export function newBuildExecutionListResponseStep<
|
|
|
220
190
|
return {
|
|
221
191
|
name: "BuildExecutionListResponse",
|
|
222
192
|
execute(ctx) {
|
|
223
|
-
const
|
|
193
|
+
const page = requirePage(ctx.get(EXECUTION_LIST_KEY));
|
|
224
194
|
ctx.set(
|
|
225
195
|
EXECUTION_LIST_KEY,
|
|
226
196
|
create(AgentExecutionListSchema, {
|
|
227
|
-
totalPages: 1,
|
|
228
|
-
entries:
|
|
197
|
+
totalPages: page.nextPageToken === "" ? 1 : 0,
|
|
198
|
+
entries: page.entries,
|
|
199
|
+
nextPageToken: page.nextPageToken,
|
|
229
200
|
}),
|
|
230
201
|
);
|
|
231
202
|
},
|
|
232
203
|
};
|
|
233
204
|
}
|
|
234
205
|
|
|
235
|
-
function
|
|
236
|
-
|
|
206
|
+
function decodeExecution(
|
|
207
|
+
data: Uint8Array,
|
|
208
|
+
logger: Logger,
|
|
209
|
+
): AgentExecution | undefined {
|
|
210
|
+
try {
|
|
211
|
+
return fromBinary(AgentExecutionSchema, data);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
logger.warn("Failed to unmarshal execution, skipping", {
|
|
214
|
+
error: error instanceof Error ? error.message : String(error),
|
|
215
|
+
});
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function decodeExecutions(
|
|
221
|
+
rows: ReadonlyArray<Uint8Array>,
|
|
222
|
+
logger: Logger,
|
|
223
|
+
): AgentExecution[] {
|
|
224
|
+
const executions: AgentExecution[] = [];
|
|
225
|
+
for (const data of rows) {
|
|
226
|
+
const execution = decodeExecution(data, logger);
|
|
227
|
+
if (execution !== undefined) {
|
|
228
|
+
executions.push(execution);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return executions;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function requirePage(value: unknown): ListPage<AgentExecution> {
|
|
235
|
+
if (
|
|
236
|
+
typeof value !== "object" ||
|
|
237
|
+
value === null ||
|
|
238
|
+
!Array.isArray((value as { entries?: unknown }).entries)
|
|
239
|
+
) {
|
|
237
240
|
throw internalError(
|
|
238
241
|
new Error("execution list not found in context"),
|
|
239
242
|
"execution list not found in context",
|
|
240
243
|
);
|
|
241
244
|
}
|
|
242
|
-
return value as AgentExecution
|
|
245
|
+
return value as ListPage<AgentExecution>;
|
|
243
246
|
}
|
|
@@ -67,6 +67,7 @@ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/
|
|
|
67
67
|
import type { Logger } from "../../boot/logger.js";
|
|
68
68
|
import type { Authorizer } from "../../extensions/authorizer.js";
|
|
69
69
|
import type { ListReadScope } from "../../extensions/list-read-scope.js";
|
|
70
|
+
import { restrictListByReadScope } from "../../extensions/list-read-scope.js";
|
|
70
71
|
import { internalError, invalidArgumentError } from "../../pipeline/errors.js";
|
|
71
72
|
import type { PipelineStep } from "../../pipeline/pipeline.js";
|
|
72
73
|
import { newPipeline } from "../../pipeline/pipeline.js";
|
|
@@ -74,8 +75,11 @@ import type { CallerIdentity } from "../../extensions/identity.js";
|
|
|
74
75
|
import { RequestContext } from "../../pipeline/request-context.js";
|
|
75
76
|
import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
|
|
76
77
|
import type { Store } from "../../store/interface.js";
|
|
78
|
+
import { listIndexInstantOfMillis } from "../../store/list-index.js";
|
|
79
|
+
import type { ListIndexRow } from "../../store/list-index.js";
|
|
77
80
|
|
|
78
81
|
import { executionUsageReportNotFoundMessage } from "./constants.js";
|
|
82
|
+
import { agentExecutionListIndex } from "./list-index.js";
|
|
79
83
|
import { EXECUTION_LIST_KEY, loadAllAgentExecutions } from "./steps.js";
|
|
80
84
|
|
|
81
85
|
export interface UsageReportDeps {
|
|
@@ -781,13 +785,11 @@ function requireReport<T>(value: unknown, message: string): T {
|
|
|
781
785
|
// absent from this shape (AD-DASH-005: the dashboard sources cost from
|
|
782
786
|
// getOrgUsageReport to prevent double-counting).
|
|
783
787
|
//
|
|
784
|
-
//
|
|
785
|
-
//
|
|
786
|
-
//
|
|
787
|
-
//
|
|
788
|
-
//
|
|
789
|
-
// header (workflowexecution/get-execution-summary.ts) for the full
|
|
790
|
-
// rationale. No scope composed = this full scan, byte-identical.
|
|
788
|
+
// The rows are the requested org's within the time window, read through
|
|
789
|
+
// the list index; with a composed ListReadScope they are offered to its
|
|
790
|
+
// restrict verb, and none visible answers the default instance — the Java
|
|
791
|
+
// AgentExecutionGetExecutionSummaryHandler baseline; see the twin's header
|
|
792
|
+
// (workflowexecution/get-execution-summary.ts) for the full rationale.
|
|
791
793
|
// ---------------------------------------------------------------------------
|
|
792
794
|
|
|
793
795
|
export async function getExecutionSummary(
|
|
@@ -795,25 +797,44 @@ export async function getExecutionSummary(
|
|
|
795
797
|
req: GetAgentExecutionSummaryRequest,
|
|
796
798
|
identity: CallerIdentity,
|
|
797
799
|
): Promise<AgentExecutionSummary> {
|
|
798
|
-
|
|
800
|
+
// The store's window is one millisecond wider than the loop's check
|
|
801
|
+
// below, which stays the authority: it only ever narrows the read.
|
|
802
|
+
const cutoffMs = resolveAgentTimeCutoffMs(req.timeWindow);
|
|
803
|
+
let rows: ListIndexRow[];
|
|
804
|
+
try {
|
|
805
|
+
rows = await deps.store.queryResources(agentExecutionListIndex, {
|
|
806
|
+
org: req.org,
|
|
807
|
+
...(cutoffMs === undefined
|
|
808
|
+
? {}
|
|
809
|
+
: { createdAtOrAfter: listIndexInstantOfMillis(cutoffMs - 1) }),
|
|
810
|
+
});
|
|
811
|
+
} catch (error) {
|
|
812
|
+
throw internalError(error, "failed to list agent executions");
|
|
813
|
+
}
|
|
814
|
+
let executions: AgentExecution[] = [];
|
|
815
|
+
for (const row of rows) {
|
|
816
|
+
try {
|
|
817
|
+
executions.push(fromBinary(AgentExecutionSchema, row.data));
|
|
818
|
+
} catch (error) {
|
|
819
|
+
deps.logger.warn("Failed to unmarshal execution, skipping", {
|
|
820
|
+
error: error instanceof Error ? error.message : String(error),
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
}
|
|
799
824
|
|
|
800
825
|
if (deps.listReadScope !== undefined) {
|
|
801
|
-
|
|
826
|
+
executions = await restrictListByReadScope(
|
|
827
|
+
deps.listReadScope,
|
|
802
828
|
identity,
|
|
803
829
|
ApiResourceKind.agent_execution,
|
|
830
|
+
executions,
|
|
831
|
+
"",
|
|
804
832
|
);
|
|
805
|
-
if (
|
|
833
|
+
if (executions.length === 0) {
|
|
806
834
|
return create(AgentExecutionSummarySchema);
|
|
807
835
|
}
|
|
808
|
-
executions = executions.filter(
|
|
809
|
-
(execution) =>
|
|
810
|
-
authorizedIds.has(execution.metadata?.id ?? "") &&
|
|
811
|
-
execution.metadata?.org === req.org,
|
|
812
|
-
);
|
|
813
836
|
}
|
|
814
837
|
|
|
815
|
-
const cutoffMs = resolveAgentTimeCutoffMs(req.timeWindow);
|
|
816
|
-
|
|
817
838
|
const phaseCounts: { [key: number]: number } = {};
|
|
818
839
|
let activeCount = 0;
|
|
819
840
|
const completedDurationsMs: number[] = [];
|
|
@@ -80,6 +80,8 @@ import {
|
|
|
80
80
|
} from "../../pipeline/steps/load-target.js";
|
|
81
81
|
import { newValidateProtoStep } from "../../pipeline/steps/validation.js";
|
|
82
82
|
import type { Store } from "../../store/interface.js";
|
|
83
|
+
import type { ListIndexRow } from "../../store/list-index.js";
|
|
84
|
+
import { artifactListIndex } from "./list-index.js";
|
|
83
85
|
import {
|
|
84
86
|
ARTIFACT_KIND,
|
|
85
87
|
DEFAULT_MAX_CONTENT_BYTES,
|
|
@@ -457,36 +459,44 @@ async function listByExecution(
|
|
|
457
459
|
.addStep({
|
|
458
460
|
name: "QueryArtifactsByExecution",
|
|
459
461
|
async execute(stepCtx): Promise<void> {
|
|
462
|
+
// The execution's artifacts through the list index's source key,
|
|
463
|
+
// whole and newest first: an execution's artifacts are returned as
|
|
464
|
+
// one (the request's paging fields are not read).
|
|
460
465
|
const input = stepCtx.input;
|
|
461
|
-
let
|
|
466
|
+
let rows: ListIndexRow[];
|
|
462
467
|
try {
|
|
463
|
-
|
|
468
|
+
rows = await deps.store.queryResources(artifactListIndex, {
|
|
469
|
+
anyKey: [
|
|
470
|
+
...(input.workflowExecutionId === ""
|
|
471
|
+
? []
|
|
472
|
+
: [
|
|
473
|
+
{
|
|
474
|
+
name: "workflow_execution" as const,
|
|
475
|
+
value: input.workflowExecutionId,
|
|
476
|
+
},
|
|
477
|
+
]),
|
|
478
|
+
...(input.agentExecutionId === ""
|
|
479
|
+
? []
|
|
480
|
+
: [
|
|
481
|
+
{
|
|
482
|
+
name: "agent_execution" as const,
|
|
483
|
+
value: input.agentExecutionId,
|
|
484
|
+
},
|
|
485
|
+
]),
|
|
486
|
+
],
|
|
487
|
+
});
|
|
464
488
|
} catch (error) {
|
|
465
489
|
throw internalError(error, "failed to list artifacts");
|
|
466
490
|
}
|
|
467
491
|
|
|
468
492
|
const artifacts: Artifact[] = [];
|
|
469
|
-
for (const
|
|
470
|
-
let artifact: Artifact;
|
|
493
|
+
for (const row of rows) {
|
|
471
494
|
try {
|
|
472
|
-
|
|
495
|
+
artifacts.push(fromBinary(ArtifactSchema, row.data));
|
|
473
496
|
} catch (error) {
|
|
474
497
|
deps.logger.warn("failed to unmarshal artifact, skipping", {
|
|
475
498
|
error: error instanceof Error ? error.message : String(error),
|
|
476
499
|
});
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
const source = artifact.spec?.source;
|
|
480
|
-
if (
|
|
481
|
-
input.workflowExecutionId !== "" &&
|
|
482
|
-
source?.workflowExecutionId === input.workflowExecutionId
|
|
483
|
-
) {
|
|
484
|
-
artifacts.push(artifact);
|
|
485
|
-
} else if (
|
|
486
|
-
input.agentExecutionId !== "" &&
|
|
487
|
-
source?.agentExecutionId === input.agentExecutionId
|
|
488
|
-
) {
|
|
489
|
-
artifacts.push(artifact);
|
|
490
500
|
}
|
|
491
501
|
}
|
|
492
502
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The artifact list index (store/list-index.ts): an artifact names the
|
|
3
|
+
* execution that produced it, agent or workflow, and
|
|
4
|
+
* `listByExecution` reads the one the request names.
|
|
5
|
+
*
|
|
6
|
+
* A change to `keys` bumps `revision` (boot/__tests__/list-indexes.test.ts
|
|
7
|
+
* pins the pair).
|
|
8
|
+
*/
|
|
9
|
+
import { ArtifactSchema } from "@stigmer/protos/ai/stigmer/agentic/artifact/v1/api_pb";
|
|
10
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
11
|
+
|
|
12
|
+
import { declareListIndex, field } from "../../store/list-index.js";
|
|
13
|
+
|
|
14
|
+
export const artifactListIndex = declareListIndex({
|
|
15
|
+
kind: ApiResourceKind.artifact,
|
|
16
|
+
schema: ArtifactSchema,
|
|
17
|
+
revision: 1,
|
|
18
|
+
keys: {
|
|
19
|
+
agent_execution: field("spec.source.agent_execution_id"),
|
|
20
|
+
workflow_execution: field("spec.source.workflow_execution_id"),
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session list index (store/list-index.ts): `agent_instance` is the
|
|
3
|
+
* key `listByAgentInstance` reads, `channel` the one `listByChannel` reads
|
|
4
|
+
* (the channel's id is stamped as a label by the channel lanes, not held
|
|
5
|
+
* in the spec). A session belongs to one organization, which is every
|
|
6
|
+
* declaration's first fact, so `session.list` narrows by it when the
|
|
7
|
+
* request names one.
|
|
8
|
+
*
|
|
9
|
+
* A change to `keys` bumps `revision` (boot/__tests__/list-indexes.test.ts
|
|
10
|
+
* pins the pair).
|
|
11
|
+
*/
|
|
12
|
+
import { SessionSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
|
|
13
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
14
|
+
|
|
15
|
+
import { declareListIndex, field, label } from "../../store/list-index.js";
|
|
16
|
+
|
|
17
|
+
/** The label the channel lanes stamp on every session they open. */
|
|
18
|
+
export const SESSION_CHANNEL_ID_LABEL = "stigmer.ai/channel-id";
|
|
19
|
+
|
|
20
|
+
export const sessionListIndex = declareListIndex({
|
|
21
|
+
kind: ApiResourceKind.session,
|
|
22
|
+
schema: SessionSchema,
|
|
23
|
+
revision: 1,
|
|
24
|
+
keys: {
|
|
25
|
+
agent_instance: field("spec.agent_instance_id"),
|
|
26
|
+
channel: label(SESSION_CHANNEL_ID_LABEL),
|
|
27
|
+
},
|
|
28
|
+
});
|