@modelprofile.com/browser-runtime 5.8.0 → 6.0.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/changelog.md +27 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.framed.d.ts +2 -0
- package/dist_ts/classes.framed.js +5 -1
- package/dist_ts/classes.runtime.d.ts +13 -0
- package/dist_ts/classes.runtime.js +98 -30
- package/dist_ts/interfaces.d.ts +5 -1
- package/package.json +2 -2
- package/readme.md +6 -4
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.framed.ts +5 -0
- package/ts/classes.runtime.ts +115 -30
- package/ts/interfaces.ts +5 -1
package/dist_ts/interfaces.d.ts
CHANGED
|
@@ -24,7 +24,11 @@ export interface IBrowserResourceKey {
|
|
|
24
24
|
export interface IBrowserAttachmentBinding {
|
|
25
25
|
attachmentAuthorityId: string;
|
|
26
26
|
attachmentRevision: number;
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* The set of sessions attached to the resource. Order is not significant and duplicates are
|
|
29
|
+
* rejected. An empty set is detached, and revision `0` requires an empty set.
|
|
30
|
+
*/
|
|
31
|
+
sessionIds: TQualifiedBrowserSessionId[];
|
|
28
32
|
}
|
|
29
33
|
export interface ICreateBrowserResourceRequest {
|
|
30
34
|
projectId: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelprofile.com/browser-runtime",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Parent-owned, resource-centric Chromium runtime with revisioned attachment fencing, authenticated human and agent control, fail-closed egress, bounded artifacts, and Flex/MCP adapters.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@git.zone/tsbuild": "4.4.3",
|
|
37
37
|
"@git.zone/tsrun": "2.0.6",
|
|
38
|
-
"@git.zone/tstest": "6.
|
|
38
|
+
"@git.zone/tstest": "6.1.1",
|
|
39
39
|
"@types/node": "26.5.0"
|
|
40
40
|
},
|
|
41
41
|
"files": [
|
package/readme.md
CHANGED
|
@@ -86,7 +86,7 @@ try {
|
|
|
86
86
|
attachmentBinding: {
|
|
87
87
|
attachmentAuthorityId: 'controller-attachment-1',
|
|
88
88
|
attachmentRevision: 1,
|
|
89
|
-
|
|
89
|
+
sessionIds: [{ harnessId: 'opencode', nativeId: 'session-456' }],
|
|
90
90
|
},
|
|
91
91
|
});
|
|
92
92
|
|
|
@@ -149,7 +149,7 @@ To downgrade, first stop the new runtime and verify that no process still uses a
|
|
|
149
149
|
|
|
150
150
|
## Attachment Fencing
|
|
151
151
|
|
|
152
|
-
Attachment bindings are Controller-owned `{ attachmentAuthorityId, attachmentRevision,
|
|
152
|
+
Attachment bindings are Controller-owned `{ attachmentAuthorityId, attachmentRevision, sessionIds }` values. `sessionIds` is the set of sessions attached to the resource, so one browser resource can serve several conversations at once. Order is not significant, duplicate members are rejected with `INVALID_INPUT`, and a set larger than 64 members is rejected with `QUOTA_EXCEEDED`. An empty set is detached; revision `0` is the initial detached/no-agent-authority state and requires an empty set. Reapplying the identical revision and set is idempotent, including in a different order; lower revisions and conflicting equal revisions fail.
|
|
153
153
|
|
|
154
154
|
```typescript
|
|
155
155
|
await runtime.applyAttachmentBinding({
|
|
@@ -158,12 +158,14 @@ await runtime.applyAttachmentBinding({
|
|
|
158
158
|
attachmentBinding: {
|
|
159
159
|
attachmentAuthorityId: 'controller-attachment-1',
|
|
160
160
|
attachmentRevision: 2,
|
|
161
|
-
|
|
161
|
+
sessionIds: [],
|
|
162
162
|
},
|
|
163
163
|
});
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
-
A newer binding fences
|
|
166
|
+
A newer binding fences only the sessions that left the set. Their leases and in-flight operations are aborted with `CAPABILITY_REVOKED` naming the session, their framed channels are closed, and their agent capabilities are revoked. Sessions that stay in the set keep their leases, framed channels and capabilities: nothing of theirs is aborted, closed or revoked, so once the rebind completes they carry on exactly as before and adding a session never interrupts the sessions already attached. While the rebind is still fencing, a session that is still in the set is never revoked: both issuing a new capability and operating an existing lease fail with `BUSY`, which is retryable, and the same call succeeds once the rebind settles. From the moment the rebind commits the new set, a session it removed is told it is gone — `CAPABILITY_REVOKED` — for the rest of that rebind, so the two outcomes are never confused. Before that commit the removal has not happened yet and may still not happen, so a session the queued rebind will remove also reports `BUSY` and should retry. Detaching with an empty set therefore releases every attached session. Human viewers retain their resource authority and subscriptions. The incarnation is preserved unless an operation cannot quiesce; that failure requires terminating the resource's browser process.
|
|
167
|
+
|
|
168
|
+
The revision fence is per session: a capability survives exactly as long as its session stays continuously attached; leaving and rejoining invalidates the old ones. Concretely, a capability is admitted while its session is a current member and the capability's `attachmentRevision` is at least the revision at which that session joined the set and no newer than the current revision. A session that leaves and later rejoins gets the newer join revision, so every capability it held before it left is rejected. Human capabilities carry no session and keep the exact-revision fence.
|
|
167
169
|
|
|
168
170
|
Agent capabilities require the exact current non-detached qualified session. Human capabilities carry no session ID and may be issued while detached. Issuance validates the current attachment binding, but an issued human capability remains valid across attachment changes: hosts must authorize it against its project, resource, actor and peer rather than the resource's later agent assignment.
|
|
169
171
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@modelprofile.com/browser-runtime',
|
|
6
|
-
version: '
|
|
6
|
+
version: '6.0.0',
|
|
7
7
|
description: 'Parent-owned, resource-centric Chromium runtime with revisioned attachment fencing, authenticated human and agent control, fail-closed egress, bounded artifacts, and Flex/MCP adapters.'
|
|
8
8
|
}
|
package/ts/classes.framed.ts
CHANGED
|
@@ -292,6 +292,11 @@ export class BrowserRuntimeFramedServerPeer {
|
|
|
292
292
|
return this.trustedChannelId;
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
+
/** The single session this peer acts for. Used to fence only the sessions a rebind removed. */
|
|
296
|
+
public get sessionId(): IBrowserFlexSessionId {
|
|
297
|
+
return { ...this.trustedSessionId };
|
|
298
|
+
}
|
|
299
|
+
|
|
295
300
|
/** @internal */
|
|
296
301
|
public get activeCapabilityId(): string | undefined {
|
|
297
302
|
return this.capabilityId ?? this.pendingCapabilityId;
|
package/ts/classes.runtime.ts
CHANGED
|
@@ -61,6 +61,8 @@ import { BrowserRuntimeFramedServerPeer } from './classes.framed.js';
|
|
|
61
61
|
|
|
62
62
|
// @push.rocks/smartpuppeteer LiveBrowserSession rejects screencast bounds above this area.
|
|
63
63
|
const maxScreencastPixelArea = 8_294_400;
|
|
64
|
+
/** Upper bound on how many conversations may share one browser resource at once. */
|
|
65
|
+
const maximumAttachedSessions = 64;
|
|
64
66
|
|
|
65
67
|
interface INormalizedScreencastOptions {
|
|
66
68
|
quality: number;
|
|
@@ -259,7 +261,16 @@ interface IResourceSlot {
|
|
|
259
261
|
permanentlyFenced: boolean;
|
|
260
262
|
retirementPending: boolean;
|
|
261
263
|
attachmentFenceCount: number;
|
|
264
|
+
/**
|
|
265
|
+
* Revision at which each currently attached session joined the set, keyed by session key. Only
|
|
266
|
+
* current members are present, so this doubles as the membership index. A member that stays
|
|
267
|
+
* across a rebind keeps its original join revision; a member that leaves and rejoins gets the
|
|
268
|
+
* new one, which is what invalidates its older capabilities.
|
|
269
|
+
*/
|
|
270
|
+
sessionJoinRevisions: Map<string, number>;
|
|
262
271
|
attachmentRetryBinding?: IBrowserAttachmentBinding;
|
|
272
|
+
/** Sessions that left the set in the rebind `attachmentRetryBinding` is retrying. */
|
|
273
|
+
attachmentRetryRemovedKeys?: Set<string>;
|
|
263
274
|
session?: ILiveBrowserSessionLike;
|
|
264
275
|
proxy?: BrowserEgressProxy;
|
|
265
276
|
profileDirectory?: string;
|
|
@@ -743,6 +754,9 @@ export class BrowserRuntime {
|
|
|
743
754
|
permanentlyFenced: false,
|
|
744
755
|
retirementPending: false,
|
|
745
756
|
attachmentFenceCount: 0,
|
|
757
|
+
sessionJoinRevisions: new Map(attachmentBinding.sessionIds.map((session) => (
|
|
758
|
+
[this.sessionKey(session), attachmentBinding.attachmentRevision]
|
|
759
|
+
))),
|
|
746
760
|
leases: new Map(),
|
|
747
761
|
frameSubscriptions: new Map(),
|
|
748
762
|
highestFrameSequence: 0,
|
|
@@ -4037,34 +4051,65 @@ export class BrowserRuntime {
|
|
|
4037
4051
|
if (slot.attachmentRetryBinding && !this.bindingsEqual(binding, slot.attachmentRetryBinding)) {
|
|
4038
4052
|
throw new BrowserRuntimeError('BUSY');
|
|
4039
4053
|
}
|
|
4054
|
+
// Members that stay keep their original join revision, so their capabilities stay valid and
|
|
4055
|
+
// nothing of theirs is torn down. Only the sessions that left the set are fenced.
|
|
4056
|
+
const nextJoinRevisions = new Map<string, number>();
|
|
4057
|
+
for (const session of binding.sessionIds) {
|
|
4058
|
+
const key = this.sessionKey(session);
|
|
4059
|
+
nextJoinRevisions.set(
|
|
4060
|
+
key,
|
|
4061
|
+
slot.sessionJoinRevisions.get(key) ?? binding.attachmentRevision,
|
|
4062
|
+
);
|
|
4063
|
+
}
|
|
4064
|
+
const removedKeys = slot.attachmentRetryRemovedKeys ?? new Set(
|
|
4065
|
+
[...slot.sessionJoinRevisions.keys()].filter((key) => !nextJoinRevisions.has(key)),
|
|
4066
|
+
);
|
|
4067
|
+
const revokedFor = (key: string): BrowserRuntimeError => new BrowserRuntimeError(
|
|
4068
|
+
'CAPABILITY_REVOKED',
|
|
4069
|
+
`attachment released session ${key}`,
|
|
4070
|
+
);
|
|
4040
4071
|
const release = await slot.mutex.acquire();
|
|
4041
4072
|
try {
|
|
4042
4073
|
slot.attachmentBinding = binding;
|
|
4043
4074
|
slot.attachmentRetryBinding = binding;
|
|
4075
|
+
slot.attachmentRetryRemovedKeys = removedKeys;
|
|
4076
|
+
slot.sessionJoinRevisions = nextJoinRevisions;
|
|
4044
4077
|
for (const lease of slot.leases.values()) {
|
|
4045
|
-
if (lease.role
|
|
4046
|
-
|
|
4047
|
-
|
|
4078
|
+
if (lease.role !== 'agent') continue;
|
|
4079
|
+
const key = this.leaseSessionKey(lease);
|
|
4080
|
+
if (key === undefined || !removedKeys.has(key)) continue;
|
|
4081
|
+
lease.controller.abort(revokedFor(key));
|
|
4048
4082
|
}
|
|
4049
4083
|
for (const operation of slot.operations) {
|
|
4050
|
-
if (operation.lease.role
|
|
4084
|
+
if (operation.lease.role !== 'agent') continue;
|
|
4085
|
+
const key = this.leaseSessionKey(operation.lease);
|
|
4086
|
+
if (key === undefined || !removedKeys.has(key)) continue;
|
|
4087
|
+
operation.controller.abort(revokedFor(key));
|
|
4051
4088
|
}
|
|
4052
4089
|
} finally {
|
|
4053
4090
|
release();
|
|
4054
4091
|
}
|
|
4055
4092
|
const peerResults = await Promise.allSettled(
|
|
4056
|
-
this.framedPeersForResource(slot)
|
|
4093
|
+
this.framedPeersForResource(slot)
|
|
4094
|
+
.filter((peer) => removedKeys.has(this.sessionKey(peer.sessionId)))
|
|
4095
|
+
.map((peer) => peer.close()),
|
|
4057
4096
|
);
|
|
4058
4097
|
const capabilityResults = await Promise.allSettled(
|
|
4059
|
-
this.capabilitiesForResource(slot).filter((record) =>
|
|
4060
|
-
.
|
|
4098
|
+
this.capabilitiesForResource(slot).filter((record) => (
|
|
4099
|
+
record.role === 'agent'
|
|
4100
|
+
&& record.sessionId !== undefined
|
|
4101
|
+
&& removedKeys.has(this.sessionKey(record.sessionId))
|
|
4102
|
+
)).map((record) => this.revokeCapabilityRecord(record)),
|
|
4061
4103
|
);
|
|
4062
4104
|
const errors = [...peerResults, ...capabilityResults]
|
|
4063
4105
|
.filter((result): result is PromiseRejectedResult => result.status === 'rejected')
|
|
4064
4106
|
.map((result) => result.reason);
|
|
4065
4107
|
const finalRelease = await slot.mutex.acquire();
|
|
4066
4108
|
try {
|
|
4067
|
-
if (errors.length === 0)
|
|
4109
|
+
if (errors.length === 0) {
|
|
4110
|
+
slot.attachmentRetryBinding = undefined;
|
|
4111
|
+
slot.attachmentRetryRemovedKeys = undefined;
|
|
4112
|
+
}
|
|
4068
4113
|
} finally {
|
|
4069
4114
|
finalRelease();
|
|
4070
4115
|
}
|
|
@@ -4073,10 +4118,9 @@ export class BrowserRuntime {
|
|
|
4073
4118
|
}
|
|
4074
4119
|
|
|
4075
4120
|
private attachmentBindingKey(binding: IBrowserAttachmentBinding): string {
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
return `${binding.attachmentRevision}:${binding.attachmentAuthorityId}:${session}`;
|
|
4121
|
+
return `${binding.attachmentRevision}:${binding.attachmentAuthorityId}:${
|
|
4122
|
+
this.sessionSetKey(binding.sessionIds)
|
|
4123
|
+
}`;
|
|
4080
4124
|
}
|
|
4081
4125
|
|
|
4082
4126
|
private tombstoneIndex(value: string, seed: number): number {
|
|
@@ -4141,7 +4185,7 @@ export class BrowserRuntime {
|
|
|
4141
4185
|
if (Object.keys(record).some((key) => (
|
|
4142
4186
|
key !== 'attachmentAuthorityId'
|
|
4143
4187
|
&& key !== 'attachmentRevision'
|
|
4144
|
-
&& key !== '
|
|
4188
|
+
&& key !== 'sessionIds'
|
|
4145
4189
|
))) throw new BrowserRuntimeError('INVALID_INPUT');
|
|
4146
4190
|
const attachmentRevision = validateInteger(
|
|
4147
4191
|
record.attachmentRevision,
|
|
@@ -4149,10 +4193,21 @@ export class BrowserRuntime {
|
|
|
4149
4193
|
0,
|
|
4150
4194
|
Number.MAX_SAFE_INTEGER,
|
|
4151
4195
|
);
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4196
|
+
if (!Array.isArray(record.sessionIds)) throw new BrowserRuntimeError('INVALID_INPUT');
|
|
4197
|
+
if (record.sessionIds.length > maximumAttachedSessions) {
|
|
4198
|
+
throw new BrowserRuntimeError('QUOTA_EXCEEDED');
|
|
4199
|
+
}
|
|
4200
|
+
const sessionIds: TQualifiedBrowserSessionId[] = [];
|
|
4201
|
+
const seen = new Set<string>();
|
|
4202
|
+
for (const entry of record.sessionIds) {
|
|
4203
|
+
const session = this.validateQualifiedSessionId(entry);
|
|
4204
|
+
const key = this.sessionKey(session);
|
|
4205
|
+
// The binding is a set. A repeated member is a caller defect, not something to collapse.
|
|
4206
|
+
if (seen.has(key)) throw new BrowserRuntimeError('INVALID_INPUT');
|
|
4207
|
+
seen.add(key);
|
|
4208
|
+
sessionIds.push(session);
|
|
4209
|
+
}
|
|
4210
|
+
if (attachmentRevision === 0 && sessionIds.length > 0) {
|
|
4156
4211
|
throw new BrowserRuntimeError('INVALID_INPUT');
|
|
4157
4212
|
}
|
|
4158
4213
|
return {
|
|
@@ -4161,17 +4216,32 @@ export class BrowserRuntime {
|
|
|
4161
4216
|
'attachmentAuthorityId',
|
|
4162
4217
|
),
|
|
4163
4218
|
attachmentRevision,
|
|
4164
|
-
|
|
4219
|
+
sessionIds,
|
|
4165
4220
|
};
|
|
4166
4221
|
}
|
|
4167
4222
|
|
|
4223
|
+
private sessionKey(session: TQualifiedBrowserSessionId): string {
|
|
4224
|
+
return `${session.harnessId}:${session.nativeId}`;
|
|
4225
|
+
}
|
|
4226
|
+
|
|
4227
|
+
private leaseSessionKey(lease: ILeaseRecord): string | undefined {
|
|
4228
|
+
const session = lease.capability.sessionId;
|
|
4229
|
+
return session ? this.sessionKey(session) : undefined;
|
|
4230
|
+
}
|
|
4231
|
+
|
|
4232
|
+
/** Order-insensitive: the same members in any order are the same binding. */
|
|
4233
|
+
private sessionSetKey(sessionIdsArg: readonly TQualifiedBrowserSessionId[]): string {
|
|
4234
|
+
if (sessionIdsArg.length === 0) return 'detached';
|
|
4235
|
+
return sessionIdsArg.map((session) => this.sessionKey(session)).sort().join(',');
|
|
4236
|
+
}
|
|
4237
|
+
|
|
4168
4238
|
private bindingsEqual(
|
|
4169
4239
|
left: IBrowserAttachmentBinding,
|
|
4170
4240
|
right: IBrowserAttachmentBinding,
|
|
4171
4241
|
): boolean {
|
|
4172
4242
|
return left.attachmentAuthorityId === right.attachmentAuthorityId
|
|
4173
4243
|
&& left.attachmentRevision === right.attachmentRevision
|
|
4174
|
-
&& this.
|
|
4244
|
+
&& this.sessionSetKey(left.sessionIds) === this.sessionSetKey(right.sessionIds);
|
|
4175
4245
|
}
|
|
4176
4246
|
|
|
4177
4247
|
private sessionsEqual(
|
|
@@ -4211,9 +4281,7 @@ export class BrowserRuntime {
|
|
|
4211
4281
|
registeredAt: slot.registeredAt,
|
|
4212
4282
|
attachmentBinding: {
|
|
4213
4283
|
...slot.attachmentBinding,
|
|
4214
|
-
|
|
4215
|
-
? { ...slot.attachmentBinding.sessionId }
|
|
4216
|
-
: null,
|
|
4284
|
+
sessionIds: slot.attachmentBinding.sessionIds.map((session) => ({ ...session })),
|
|
4217
4285
|
},
|
|
4218
4286
|
incarnationStatus: slot.permanentlyFenced || slot.attachmentRetryBinding
|
|
4219
4287
|
? 'fenced'
|
|
@@ -4233,19 +4301,33 @@ export class BrowserRuntime {
|
|
|
4233
4301
|
>,
|
|
4234
4302
|
): void {
|
|
4235
4303
|
const binding = slot.attachmentBinding;
|
|
4236
|
-
if (
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4304
|
+
if (binding.attachmentAuthorityId !== expected.attachmentAuthorityId) {
|
|
4305
|
+
throw new BrowserRuntimeError('ATTACHMENT_CONFLICT');
|
|
4306
|
+
}
|
|
4307
|
+
// A capability may never claim a revision the authority has not published yet, whatever its
|
|
4308
|
+
// role. For an agent the lower bound is its own join revision, not the current one.
|
|
4309
|
+
if (expected.attachmentRevision > binding.attachmentRevision) {
|
|
4310
|
+
throw new BrowserRuntimeError('ATTACHMENT_CONFLICT');
|
|
4311
|
+
}
|
|
4240
4312
|
if (expected.role === 'agent') {
|
|
4313
|
+
if (!expected.sessionId) throw new BrowserRuntimeError('ATTACHMENT_CONFLICT');
|
|
4314
|
+
// A capability survives exactly as long as its session stays continuously attached:
|
|
4315
|
+
// membership is the presence of a join revision, and continuity is the capability having
|
|
4316
|
+
// been minted at or after it. Leaving the set drops the entry; rejoining raises it.
|
|
4317
|
+
// This is decided before the in-progress check, so a session the rebind already removed is
|
|
4318
|
+
// told it is gone rather than told to retry.
|
|
4319
|
+
const joinedAtRevision = slot.sessionJoinRevisions.get(this.sessionKey(expected.sessionId));
|
|
4320
|
+
if (joinedAtRevision === undefined || expected.attachmentRevision < joinedAtRevision) {
|
|
4321
|
+
throw new BrowserRuntimeError('ATTACHMENT_CONFLICT');
|
|
4322
|
+
}
|
|
4241
4323
|
if (slot.attachmentFenceCount > 0 || slot.attachmentRetryBinding) {
|
|
4242
4324
|
throw new BrowserRuntimeError('BUSY');
|
|
4243
4325
|
}
|
|
4244
|
-
if (!binding.sessionId || !this.sessionsEqual(binding.sessionId, expected.sessionId)) {
|
|
4245
|
-
throw new BrowserRuntimeError('ATTACHMENT_CONFLICT');
|
|
4246
|
-
}
|
|
4247
4326
|
} else if (expected.sessionId !== undefined) {
|
|
4248
4327
|
throw new BrowserRuntimeError('ATTACHMENT_CONFLICT');
|
|
4328
|
+
} else if (binding.attachmentRevision !== expected.attachmentRevision) {
|
|
4329
|
+
// Human capabilities keep the exact-revision fence; they carry no session to be continuous.
|
|
4330
|
+
throw new BrowserRuntimeError('ATTACHMENT_CONFLICT');
|
|
4249
4331
|
}
|
|
4250
4332
|
}
|
|
4251
4333
|
|
|
@@ -4260,7 +4342,10 @@ export class BrowserRuntime {
|
|
|
4260
4342
|
if (capability.role === 'human') return;
|
|
4261
4343
|
try {
|
|
4262
4344
|
this.assertCapabilityBinding(slot, capability);
|
|
4263
|
-
} catch {
|
|
4345
|
+
} catch (error) {
|
|
4346
|
+
// An in-progress rebind is transient and says nothing about this capability's authority. A
|
|
4347
|
+
// session that is still in the set must be told to retry, not that it has been revoked.
|
|
4348
|
+
if (error instanceof BrowserRuntimeError && error.code === 'BUSY') throw error;
|
|
4264
4349
|
throw new BrowserRuntimeError('CAPABILITY_REVOKED');
|
|
4265
4350
|
}
|
|
4266
4351
|
}
|
package/ts/interfaces.ts
CHANGED
|
@@ -31,7 +31,11 @@ export interface IBrowserResourceKey {
|
|
|
31
31
|
export interface IBrowserAttachmentBinding {
|
|
32
32
|
attachmentAuthorityId: string;
|
|
33
33
|
attachmentRevision: number;
|
|
34
|
-
|
|
34
|
+
/**
|
|
35
|
+
* The set of sessions attached to the resource. Order is not significant and duplicates are
|
|
36
|
+
* rejected. An empty set is detached, and revision `0` requires an empty set.
|
|
37
|
+
*/
|
|
38
|
+
sessionIds: TQualifiedBrowserSessionId[];
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
export interface ICreateBrowserResourceRequest {
|