From 3fe96c94878a01c4a114cff45cf685eecadbe72e Mon Sep 17 00:00:00 2001 From: flemming-it Date: Sun, 12 Jul 2026 14:34:24 +0200 Subject: [PATCH] feat: listInvocations() for the detached-runs monitor 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 --- CHANGELOG.md | 4 + lib/src/generated/chain/v1/hub.pb.dart | 146 +++++++++++++++++++++ lib/src/generated/chain/v1/hub.pbgrpc.dart | 30 +++++ lib/src/generated/chain/v1/hub.pbjson.dart | 44 +++++++ lib/src/hub_client.dart | 9 ++ 5 files changed, 233 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90f30d0..dd612c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/lib/src/generated/chain/v1/hub.pb.dart b/lib/src/generated/chain/v1/hub.pb.dart index 98103d7..29100c7 100644 --- a/lib/src/generated/chain/v1/hub.pb.dart +++ b/lib/src/generated/chain/v1/hub.pb.dart @@ -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(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(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? 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(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(create); + static InvocationList? _defaultInstance; + + /// Tracked detached invocations, newest-first by start time. + @$pb.TagNumber(1) + $pb.PbList get invocations => $_getList(0); +} + class ModuleList extends $pb.GeneratedMessage { factory ModuleList({ $core.Iterable? modules, diff --git a/lib/src/generated/chain/v1/hub.pbgrpc.dart b/lib/src/generated/chain/v1/hub.pbgrpc.dart index bb4e97e..26af5e7 100644 --- a/lib/src/generated/chain/v1/hub.pbgrpc.dart +++ b/lib/src/generated/chain/v1/hub.pbgrpc.dart @@ -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); diff --git a/lib/src/generated/chain/v1/hub.pbjson.dart b/lib/src/generated/chain/v1/hub.pbjson.dart index 8c9af70..de6a4d5 100644 --- a/lib/src/generated/chain/v1/hub.pbjson.dart +++ b/lib/src/generated/chain/v1/hub.pbjson.dart @@ -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', diff --git a/lib/src/hub_client.dart b/lib/src/hub_client.dart index 239e5b7..233e7c4 100644 --- a/lib/src/hub_client.dart +++ b/lib/src/hub_client.dart @@ -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 F∆I 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> listInvocations() async { + final r = await _hub.listInvocations(Empty()); + return r.invocations; + } + pb.SubmitRequest _buildSubmitRequest({ required String flowYaml, required Map textInputs,