feat: listInvocations() for the detached-runs monitor
Some checks failed
Security / Security check (push) Failing after 1s

Returns every tracked detached invocation (newest-first) with id,
phase, current step, timestamps, error, flow name and project.
Regenerated stubs from the platform hub.proto.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-07-12 14:34:24 +02:00
parent f67e430b09
commit 3fe96c9487
5 changed files with 233 additions and 0 deletions

View file

@ -6,6 +6,10 @@ for the authoritative per-release detail.
## Unreleased
- Detached-runs monitor: `listInvocations()` returns every tracked
detached invocation (newest-first) with id, phase, current step,
timestamps, error, flow name and project — backs Studio's
detached-runs list. Regenerated stubs.
- Projects on the wire (Studio multi-project stage ①):
`listProjects()` returns the shared hub's registry (`general`
first; sealed areas never appear — they are their own hub

View file

@ -853,6 +853,152 @@ class CancelInvocationResponse extends $pb.GeneratedMessage {
void clearCancelled() => $_clearField(1);
}
/// One row in the detached-runs monitor list.
class InvocationEntry extends $pb.GeneratedMessage {
factory InvocationEntry({
$core.String? invocationId,
InvocationStatus? status,
$core.String? flowName,
$core.String? project,
}) {
final result = create();
if (invocationId != null) result.invocationId = invocationId;
if (status != null) result.status = status;
if (flowName != null) result.flowName = flowName;
if (project != null) result.project = project;
return result;
}
InvocationEntry._();
factory InvocationEntry.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory InvocationEntry.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'InvocationEntry',
package: const $pb.PackageName(_omitMessageNames ? '' : 'chain.v1'),
createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'invocationId')
..aOM<InvocationStatus>(2, _omitFieldNames ? '' : 'status',
subBuilder: InvocationStatus.create)
..aOS(3, _omitFieldNames ? '' : 'flowName')
..aOS(4, _omitFieldNames ? '' : 'project')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
InvocationEntry clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
InvocationEntry copyWith(void Function(InvocationEntry) updates) =>
super.copyWith((message) => updates(message as InvocationEntry))
as InvocationEntry;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static InvocationEntry create() => InvocationEntry._();
@$core.override
InvocationEntry createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static InvocationEntry getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<InvocationEntry>(create);
static InvocationEntry? _defaultInstance;
@$pb.TagNumber(1)
$core.String get invocationId => $_getSZ(0);
@$pb.TagNumber(1)
set invocationId($core.String value) => $_setString(0, value);
@$pb.TagNumber(1)
$core.bool hasInvocationId() => $_has(0);
@$pb.TagNumber(1)
void clearInvocationId() => $_clearField(1);
@$pb.TagNumber(2)
InvocationStatus get status => $_getN(1);
@$pb.TagNumber(2)
set status(InvocationStatus value) => $_setField(2, value);
@$pb.TagNumber(2)
$core.bool hasStatus() => $_has(1);
@$pb.TagNumber(2)
void clearStatus() => $_clearField(2);
@$pb.TagNumber(2)
InvocationStatus ensureStatus() => $_ensure(1);
/// Flow name this invocation is running, empty when unknown.
@$pb.TagNumber(3)
$core.String get flowName => $_getSZ(2);
@$pb.TagNumber(3)
set flowName($core.String value) => $_setString(2, value);
@$pb.TagNumber(3)
$core.bool hasFlowName() => $_has(2);
@$pb.TagNumber(3)
void clearFlowName() => $_clearField(3);
/// Project slug the run was stamped with (`general` when none).
@$pb.TagNumber(4)
$core.String get project => $_getSZ(3);
@$pb.TagNumber(4)
set project($core.String value) => $_setString(3, value);
@$pb.TagNumber(4)
$core.bool hasProject() => $_has(3);
@$pb.TagNumber(4)
void clearProject() => $_clearField(4);
}
class InvocationList extends $pb.GeneratedMessage {
factory InvocationList({
$core.Iterable<InvocationEntry>? invocations,
}) {
final result = create();
if (invocations != null) result.invocations.addAll(invocations);
return result;
}
InvocationList._();
factory InvocationList.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory InvocationList.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'InvocationList',
package: const $pb.PackageName(_omitMessageNames ? '' : 'chain.v1'),
createEmptyInstance: create)
..pPM<InvocationEntry>(1, _omitFieldNames ? '' : 'invocations',
subBuilder: InvocationEntry.create)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
InvocationList clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
InvocationList copyWith(void Function(InvocationList) updates) =>
super.copyWith((message) => updates(message as InvocationList))
as InvocationList;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static InvocationList create() => InvocationList._();
@$core.override
InvocationList createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static InvocationList getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<InvocationList>(create);
static InvocationList? _defaultInstance;
/// Tracked detached invocations, newest-first by start time.
@$pb.TagNumber(1)
$pb.PbList<InvocationEntry> get invocations => $_getList(0);
}
class ModuleList extends $pb.GeneratedMessage {
factory ModuleList({
$core.Iterable<ModuleSummary>? modules,

View file

@ -90,6 +90,16 @@ class HubClient extends $grpc.Client {
return $createUnaryCall(_$cancelInvocation, request, options: options);
}
/// List every tracked detached invocation (newest-first) for a
/// monitor surface Studio's detached-runs list. Returns an empty
/// list (not an error) when detached invocations are disabled.
$grpc.ResponseFuture<$0.InvocationList> listInvocations(
$1.Empty request, {
$grpc.CallOptions? options,
}) {
return $createUnaryCall(_$listInvocations, request, options: options);
}
/// Liveness / readiness probe.
$grpc.ResponseFuture<$0.HealthStatus> health(
$1.Empty request, {
@ -129,6 +139,11 @@ class HubClient extends $grpc.Client {
'/chain.v1.Hub/CancelInvocation',
($2.InvocationId value) => value.writeToBuffer(),
$0.CancelInvocationResponse.fromBuffer);
static final _$listInvocations =
$grpc.ClientMethod<$1.Empty, $0.InvocationList>(
'/chain.v1.Hub/ListInvocations',
($1.Empty value) => value.writeToBuffer(),
$0.InvocationList.fromBuffer);
static final _$health = $grpc.ClientMethod<$1.Empty, $0.HealthStatus>(
'/chain.v1.Hub/Health',
($1.Empty value) => value.writeToBuffer(),
@ -183,6 +198,13 @@ abstract class HubServiceBase extends $grpc.Service {
false,
($core.List<$core.int> value) => $2.InvocationId.fromBuffer(value),
($0.CancelInvocationResponse value) => value.writeToBuffer()));
$addMethod($grpc.ServiceMethod<$1.Empty, $0.InvocationList>(
'ListInvocations',
listInvocations_Pre,
false,
false,
($core.List<$core.int> value) => $1.Empty.fromBuffer(value),
($0.InvocationList value) => value.writeToBuffer()));
$addMethod($grpc.ServiceMethod<$1.Empty, $0.HealthStatus>(
'Health',
health_Pre,
@ -240,6 +262,14 @@ abstract class HubServiceBase extends $grpc.Service {
$async.Future<$0.CancelInvocationResponse> cancelInvocation(
$grpc.ServiceCall call, $2.InvocationId request);
$async.Future<$0.InvocationList> listInvocations_Pre(
$grpc.ServiceCall $call, $async.Future<$1.Empty> $request) async {
return listInvocations($call, await $request);
}
$async.Future<$0.InvocationList> listInvocations(
$grpc.ServiceCall call, $1.Empty request);
$async.Future<$0.HealthStatus> health_Pre(
$grpc.ServiceCall $call, $async.Future<$1.Empty> $request) async {
return health($call, await $request);

View file

@ -303,6 +303,50 @@ final $typed_data.Uint8List cancelInvocationResponseDescriptor =
'ChhDYW5jZWxJbnZvY2F0aW9uUmVzcG9uc2USHAoJY2FuY2VsbGVkGAEgASgIUgljYW5jZWxsZW'
'Q=');
@$core.Deprecated('Use invocationEntryDescriptor instead')
const InvocationEntry$json = {
'1': 'InvocationEntry',
'2': [
{'1': 'invocation_id', '3': 1, '4': 1, '5': 9, '10': 'invocationId'},
{
'1': 'status',
'3': 2,
'4': 1,
'5': 11,
'6': '.chain.v1.InvocationStatus',
'10': 'status'
},
{'1': 'flow_name', '3': 3, '4': 1, '5': 9, '10': 'flowName'},
{'1': 'project', '3': 4, '4': 1, '5': 9, '10': 'project'},
],
};
/// Descriptor for `InvocationEntry`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List invocationEntryDescriptor = $convert.base64Decode(
'Cg9JbnZvY2F0aW9uRW50cnkSIwoNaW52b2NhdGlvbl9pZBgBIAEoCVIMaW52b2NhdGlvbklkEj'
'IKBnN0YXR1cxgCIAEoCzIaLmNoYWluLnYxLkludm9jYXRpb25TdGF0dXNSBnN0YXR1cxIbCglm'
'bG93X25hbWUYAyABKAlSCGZsb3dOYW1lEhgKB3Byb2plY3QYBCABKAlSB3Byb2plY3Q=');
@$core.Deprecated('Use invocationListDescriptor instead')
const InvocationList$json = {
'1': 'InvocationList',
'2': [
{
'1': 'invocations',
'3': 1,
'4': 3,
'5': 11,
'6': '.chain.v1.InvocationEntry',
'10': 'invocations'
},
],
};
/// Descriptor for `InvocationList`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List invocationListDescriptor = $convert.base64Decode(
'Cg5JbnZvY2F0aW9uTGlzdBI7CgtpbnZvY2F0aW9ucxgBIAMoCzIZLmNoYWluLnYxLkludm9jYX'
'Rpb25FbnRyeVILaW52b2NhdGlvbnM=');
@$core.Deprecated('Use moduleListDescriptor instead')
const ModuleList$json = {
'1': 'ModuleList',

View file

@ -92,6 +92,7 @@ typedef SubmitResponse = pb.SubmitResponse;
// generated package directly.
typedef SubmitStreamEvent = pb.SubmitStreamEvent;
typedef InvocationStatus = pb.InvocationStatus;
typedef InvocationEntry = pb.InvocationEntry;
/// Typed client for the FI Hub gRPC surface. Construct once,
/// reuse for the process lifetime, call [close] on shutdown.
@ -871,6 +872,14 @@ class HubClient {
return r.cancelled;
}
/// Every tracked detached invocation (newest-first) for a monitor
/// surface Studio's detached-runs list. Empty when detached
/// invocations are disabled or none have run this process.
Future<List<InvocationEntry>> listInvocations() async {
final r = await _hub.listInvocations(Empty());
return r.invocations;
}
pb.SubmitRequest _buildSubmitRequest({
required String flowYaml,
required Map<String, String> textInputs,