shennian 0.3.85 → 0.3.86
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/bin/shennian.js +1 -1
- package/dist/publish-build-manifest.json +28 -28
- package/dist/src/agents/adapter.js +19 -1
- package/dist/src/agents/claude-managed-permissions.js +248 -1
- package/dist/src/agents/claude.js +393 -8
- package/dist/src/agents/codex-managed-permissions.js +294 -1
- package/dist/src/agents/codex-managed-room-tools.js +249 -1
- package/dist/src/agents/codex-utils.js +200 -7
- package/dist/src/agents/codex.js +1352 -17
- package/dist/src/agents/config-status.js +226 -1
- package/dist/src/agents/cursor-managed-permissions.js +299 -2
- package/dist/src/agents/cursor.js +314 -1
- package/dist/src/agents/custom.js +418 -4
- package/dist/src/agents/external-channel-instructions.js +94 -11
- package/dist/src/agents/gemini.js +180 -1
- package/dist/src/agents/manager.d.ts +2 -0
- package/dist/src/agents/manager.js +250 -18
- package/dist/src/agents/model-registry/cache.js +37 -1
- package/dist/src/agents/model-registry/discovery.js +188 -2
- package/dist/src/agents/model-registry/parsers.js +521 -4
- package/dist/src/agents/model-registry/runner.js +30 -1
- package/dist/src/agents/model-registry/service.js +115 -1
- package/dist/src/agents/model-registry/types.js +8 -1
- package/dist/src/agents/model-registry.js +19 -1
- package/dist/src/agents/native-capabilities.js +237 -1
- package/dist/src/agents/openclaw.js +293 -2
- package/dist/src/agents/opencode-managed-permissions.js +187 -2
- package/dist/src/agents/opencode.js +298 -1
- package/dist/src/agents/platform-instructions.js +46 -17
- package/dist/src/agents/runtime-compatibility.js +44 -1
- package/dist/src/agents/workbuddy-managed-permissions.js +329 -2
- package/dist/src/agents/workbuddy.js +444 -3
- package/dist/src/commands/room.js +2 -2
- package/dist/src/fs/boundary.js +185 -1
- package/dist/src/fs/handler.js +130 -1
- package/dist/src/fs/security.js +32 -1
- package/dist/src/fs/text-decoder.js +110 -1
- package/dist/src/index.js +587 -3
- package/dist/src/manager/prompt.js +34 -29
- package/dist/src/manager/registry.js +317 -1
- package/dist/src/manager/retired-external-channel-compat.js +40 -1
- package/dist/src/manager/runtime.d.ts +4 -0
- package/dist/src/manager/runtime.js +1514 -23
- package/dist/src/native-fusion/codex-parser.js +660 -4
- package/dist/src/native-fusion/codex-user-input.js +102 -5
- package/dist/src/native-fusion/config.js +5 -1
- package/dist/src/native-fusion/file-watcher.js +191 -1
- package/dist/src/native-fusion/local-repository.js +569 -2
- package/dist/src/native-fusion/opencode-parser.js +130 -3
- package/dist/src/native-fusion/parsers.js +140 -3
- package/dist/src/native-fusion/service.js +739 -2
- package/dist/src/native-fusion/session-header.js +101 -2
- package/dist/src/native-fusion/state.js +104 -2
- package/dist/src/native-fusion/types.js +1 -1
- package/dist/src/personal-sync/machine-authority.js +34 -1
- package/dist/src/room/activation-audit.js +45 -2
- package/dist/src/room/activation-policy.js +30 -1
- package/dist/src/room/activation-service.d.ts +1 -0
- package/dist/src/room/activation-service.js +408 -8
- package/dist/src/room/invite-bootstrap.js +156 -1
- package/dist/src/room/managed-context.d.ts +1 -1
- package/dist/src/room/managed-context.js +1 -1
- package/dist/src/room/managed-prompt-composer.d.ts +1 -1
- package/dist/src/room/managed-prompt-composer.js +6 -5
- package/dist/src/room/managed-room-binding.js +167 -1
- package/dist/src/room/managed-room-ipc.d.ts +2 -2
- package/dist/src/room/managed-room-ipc.js +2 -2
- package/dist/src/room/mirror-sync-service.js +183 -1
- package/dist/src/room/room-action-client.d.ts +11 -0
- package/dist/src/room/room-action-client.js +1 -1
- package/dist/src/room/room-binding-client.d.ts +1 -0
- package/dist/src/room/room-binding-client.js +151 -1
- package/dist/src/security/managed-agent-policy.d.ts +3 -2
- package/dist/src/security/managed-agent-policy.js +1 -1
- package/dist/src/session/archive-zip.js +220 -1
- package/dist/src/session/deletion-sync-state.js +136 -2
- package/dist/src/session/handlers/agent-capabilities.js +14 -1
- package/dist/src/session/handlers/agent-config.js +150 -1
- package/dist/src/session/handlers/agents.js +55 -1
- package/dist/src/session/handlers/chat.d.ts +1 -1
- package/dist/src/session/handlers/chat.js +1030 -2
- package/dist/src/session/handlers/control.js +61 -1
- package/dist/src/session/handlers/fs.js +594 -1
- package/dist/src/session/handlers/message-status.js +15 -1
- package/dist/src/session/handlers/remote-access.js +43 -1
- package/dist/src/session/handlers/room-managed.js +101 -1
- package/dist/src/session/handlers/session-refresh.js +35 -1
- package/dist/src/session/handlers/title.js +79 -1
- package/dist/src/session/handlers/tool-detail.js +149 -1
- package/dist/src/session/handlers/voice-window.js +62 -1
- package/dist/src/session/history-subscriptions.js +470 -1
- package/dist/src/session/manager.js +658 -1
- package/dist/src/session/native-cleanup-outbox.js +287 -2
- package/dist/src/session/native-session-lifecycle.js +327 -6
- package/dist/src/session/queue.d.ts +5 -0
- package/dist/src/session/queue.js +8 -8
- package/dist/src/upgrade/scheduler.js +126 -1
- package/node_modules/@shennian/wire/dist/agent-workspace.js +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,408 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
// @arch docs/features/network-rooms/core-data-and-security-decisions.md#五触发语义
|
|
2
|
+
// @test src/__tests__/room-activation-service.test.ts
|
|
3
|
+
import { isAvailableBuiltinAgentType, } from '@shennian/wire';
|
|
4
|
+
import { getBuiltinAgentVersion, resolveBuiltinCommand } from '../agents/command-spec.js';
|
|
5
|
+
import { registerCustomRuntimeSecurity, resolveCustomRuntimeExecutableFingerprint, } from '../agents/custom-managed-runtime.js';
|
|
6
|
+
import { loadConfig } from '../config/index.js';
|
|
7
|
+
import { resolveManagedAgentExecutableIdentity } from '../security/managed-agent-executable.js';
|
|
8
|
+
import { compileManagedAgentPolicy, } from '../security/managed-agent-policy.js';
|
|
9
|
+
import { resolveManagedAgentRuntimeProtection } from '../security/managed-agent-runtime.js';
|
|
10
|
+
import { selectRoomActivationTargets } from './activation-policy.js';
|
|
11
|
+
import { composeManagedRoomPrompt } from './managed-prompt-composer.js';
|
|
12
|
+
const MAX_HOPS = 4;
|
|
13
|
+
const ACTIVATION_WINDOW_MS = 60_000;
|
|
14
|
+
const MAX_ACTIVATIONS_PER_WINDOW = 6;
|
|
15
|
+
const RATE_PAUSE_MS = 5 * 60_000;
|
|
16
|
+
export class RoomActivationService {
|
|
17
|
+
deps;
|
|
18
|
+
latestContexts = new Map();
|
|
19
|
+
constructor(deps) {
|
|
20
|
+
this.deps = deps;
|
|
21
|
+
}
|
|
22
|
+
async processMessages(roomId, messages, registrations, context = { roomName: '当前群聊' }) {
|
|
23
|
+
const queued = [];
|
|
24
|
+
const cursorAdvances = [];
|
|
25
|
+
const mentionContexts = new Map();
|
|
26
|
+
const legacyStateToRetire = new Set();
|
|
27
|
+
const ordered = [...messages].sort((a, b) => compareSequence(a.sequence, b.sequence));
|
|
28
|
+
for (const snapshot of registrations) {
|
|
29
|
+
const current = this.deps.registry.getByRegistryKey(snapshot.registryKey);
|
|
30
|
+
if (!current?.binding || current.binding.roomId !== roomId)
|
|
31
|
+
continue;
|
|
32
|
+
if (current.binding.status !== 'active' &&
|
|
33
|
+
current.binding.status !== 'paused' &&
|
|
34
|
+
current.binding.activationQueue.length === 0)
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
// One-way upgrade bridge: old daemons persisted Room-specific pending items.
|
|
38
|
+
// Move only items that were never dispatched into the Session queue, and
|
|
39
|
+
// clear every legacy active marker after the durable batch commit.
|
|
40
|
+
for (const snapshot of registrations) {
|
|
41
|
+
const current = this.deps.registry.getByRegistryKey(snapshot.registryKey);
|
|
42
|
+
if (!current?.binding || current.binding.roomId !== roomId)
|
|
43
|
+
continue;
|
|
44
|
+
if (current.binding.activationQueue.length === 0 && !current.binding.activeActivation)
|
|
45
|
+
continue;
|
|
46
|
+
legacyStateToRetire.add(current.registryKey);
|
|
47
|
+
if (current.binding.activationQueue.length === 0)
|
|
48
|
+
continue;
|
|
49
|
+
const observation = this.deps.observations.getBySource(current);
|
|
50
|
+
if (!observation?.workspacePath || observation.lifecycleState === 'closed') {
|
|
51
|
+
throw new Error('managed_room_workspace_unavailable');
|
|
52
|
+
}
|
|
53
|
+
this.latestContexts.set(current.registryKey, context);
|
|
54
|
+
for (const item of current.binding.activationQueue) {
|
|
55
|
+
queued.push({
|
|
56
|
+
registration: current,
|
|
57
|
+
input: {
|
|
58
|
+
params: {
|
|
59
|
+
sessionId: current.nianSessionId,
|
|
60
|
+
text: item.deliveryText ?? '群聊中有一条待处理消息,请结合当前群聊上下文处理。',
|
|
61
|
+
agentType: toAgentType(current.runtimeAdapter),
|
|
62
|
+
sessionMode: context.agentsByIdentityId?.[current.binding.agentIdentityId]?.sessionMode ?? 'standard',
|
|
63
|
+
managerConfig: context.agentsByIdentityId?.[current.binding.agentIdentityId]?.managerConfig ?? null,
|
|
64
|
+
managerDefaultWorkerAgentType: context.agentsByIdentityId?.[current.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType ?? null,
|
|
65
|
+
managerDefaultWorkerModelId: context.agentsByIdentityId?.[current.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId ?? null,
|
|
66
|
+
workDir: observation.workspacePath,
|
|
67
|
+
agentSessionId: current.sourceSessionKey,
|
|
68
|
+
queueMessageId: `room-activation-${current.binding.bindingId}-${item.causeMessageId}`,
|
|
69
|
+
clientMessageId: `room-activation-${current.binding.bindingId}-${item.causeMessageId}`,
|
|
70
|
+
},
|
|
71
|
+
context: {
|
|
72
|
+
registryKey: current.registryKey,
|
|
73
|
+
causeMessageId: item.causeMessageId,
|
|
74
|
+
rootCauseMessageId: item.rootCauseMessageId,
|
|
75
|
+
sequence: item.sequence,
|
|
76
|
+
hopCount: item.hopCount,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const message of ordered) {
|
|
83
|
+
const targets = new Set(selectRoomActivationTargets(message, registrations).map((target) => target.registration.registryKey));
|
|
84
|
+
for (const snapshot of registrations) {
|
|
85
|
+
const current = this.deps.registry.getByRegistryKey(snapshot.registryKey);
|
|
86
|
+
if (!current?.binding || current.binding.roomId !== roomId)
|
|
87
|
+
continue;
|
|
88
|
+
if (current.binding.status !== 'active' && current.binding.status !== 'paused')
|
|
89
|
+
continue;
|
|
90
|
+
if (BigInt(message.sequence) <= BigInt(current.binding.lastActivatedSequence))
|
|
91
|
+
continue;
|
|
92
|
+
const mentionContext = mentionContexts.get(current.registryKey)
|
|
93
|
+
?? [...(current.binding.mentionContext ?? [])];
|
|
94
|
+
mentionContexts.set(current.registryKey, mentionContext);
|
|
95
|
+
if (targets.has(current.registryKey)) {
|
|
96
|
+
const hopCount = (message.activationCause?.hopCount ?? 0) +
|
|
97
|
+
(message.author.principalType === 'agent' ? 1 : 0);
|
|
98
|
+
const rootCauseMessageId = message.activationCause?.rootCauseMessageId ?? message.id;
|
|
99
|
+
if (hopCount > MAX_HOPS) {
|
|
100
|
+
this.audit(current, message.id, rootCauseMessageId, hopCount, 'suppressed', 'max_hops');
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const claim = this.deps.registry.claimActivation({
|
|
104
|
+
registryKey: current.registryKey,
|
|
105
|
+
windowMs: ACTIVATION_WINDOW_MS,
|
|
106
|
+
maxActivations: MAX_ACTIVATIONS_PER_WINDOW,
|
|
107
|
+
pauseMs: RATE_PAUSE_MS,
|
|
108
|
+
now: this.deps.now?.(),
|
|
109
|
+
});
|
|
110
|
+
if (!claim.allowed) {
|
|
111
|
+
if (claim.newlyPaused) {
|
|
112
|
+
this.audit(current, message.id, rootCauseMessageId, hopCount, 'suppressed', 'rate_limited');
|
|
113
|
+
}
|
|
114
|
+
cursorAdvances.push({ registryKey: current.registryKey, sequence: message.sequence });
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const observation = this.deps.observations.getBySource(current);
|
|
118
|
+
if (!observation?.workspacePath || observation.lifecycleState === 'closed') {
|
|
119
|
+
throw new Error('managed_room_workspace_unavailable');
|
|
120
|
+
}
|
|
121
|
+
const currentDelivery = buildDeliveryText([message]);
|
|
122
|
+
const deliveryText = current.binding.activationMode === 'mention_only'
|
|
123
|
+
? mergeDeliveryTexts([
|
|
124
|
+
...mentionContext.map((item) => item.deliveryText),
|
|
125
|
+
currentDelivery,
|
|
126
|
+
])
|
|
127
|
+
: currentDelivery;
|
|
128
|
+
this.latestContexts.set(current.registryKey, context);
|
|
129
|
+
queued.push({
|
|
130
|
+
registration: current,
|
|
131
|
+
input: {
|
|
132
|
+
params: {
|
|
133
|
+
sessionId: current.nianSessionId,
|
|
134
|
+
text: deliveryText,
|
|
135
|
+
agentType: toAgentType(current.runtimeAdapter),
|
|
136
|
+
sessionMode: context.agentsByIdentityId?.[current.binding.agentIdentityId]?.sessionMode ?? 'standard',
|
|
137
|
+
managerConfig: context.agentsByIdentityId?.[current.binding.agentIdentityId]?.managerConfig ?? null,
|
|
138
|
+
managerDefaultWorkerAgentType: context.agentsByIdentityId?.[current.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType ?? null,
|
|
139
|
+
managerDefaultWorkerModelId: context.agentsByIdentityId?.[current.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId ?? null,
|
|
140
|
+
workDir: observation.workspacePath,
|
|
141
|
+
agentSessionId: current.sourceSessionKey,
|
|
142
|
+
queueMessageId: `room-activation-${current.binding.bindingId}-${message.id}`,
|
|
143
|
+
clientMessageId: `room-activation-${current.binding.bindingId}-${message.id}`,
|
|
144
|
+
},
|
|
145
|
+
context: {
|
|
146
|
+
registryKey: current.registryKey,
|
|
147
|
+
causeMessageId: message.id,
|
|
148
|
+
rootCauseMessageId,
|
|
149
|
+
sequence: message.sequence,
|
|
150
|
+
hopCount,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
if (current.binding.activationMode === 'mention_only') {
|
|
155
|
+
mentionContexts.set(current.registryKey, []);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else if (current.binding.activationMode === 'mention_only' &&
|
|
160
|
+
message.kind !== 'system' &&
|
|
161
|
+
message.author.membershipId !== current.binding.agentMembershipId) {
|
|
162
|
+
if (!mentionContext.some((item) => item.sequence === message.sequence)) {
|
|
163
|
+
mentionContext.push({ sequence: message.sequence, deliveryText: buildDeliveryText([message]) });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
cursorAdvances.push({ registryKey: current.registryKey, sequence: message.sequence });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
await this.deps.enqueueConversationBatch(queued.map((entry) => entry.input));
|
|
170
|
+
for (const registryKey of legacyStateToRetire) {
|
|
171
|
+
this.deps.registry.retireLegacyActivationState(registryKey, this.deps.now?.() ?? new Date());
|
|
172
|
+
}
|
|
173
|
+
for (const [registryKey, items] of mentionContexts) {
|
|
174
|
+
this.deps.registry.replaceMentionContext(registryKey, items, this.deps.now?.() ?? new Date());
|
|
175
|
+
}
|
|
176
|
+
for (const entry of queued) {
|
|
177
|
+
this.audit(entry.registration, entry.input.context.causeMessageId, entry.input.context.rootCauseMessageId, entry.input.context.hopCount, 'queued', 'conversation_queue');
|
|
178
|
+
}
|
|
179
|
+
for (const advance of cursorAdvances) {
|
|
180
|
+
this.deps.registry.advanceCursor({
|
|
181
|
+
registryKey: advance.registryKey,
|
|
182
|
+
cursor: 'activated',
|
|
183
|
+
sequence: advance.sequence,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async prepareConversationTurn(contexts) {
|
|
188
|
+
const currentContext = contexts.at(-1);
|
|
189
|
+
if (!currentContext)
|
|
190
|
+
throw new Error('managed_room_queue_context_missing');
|
|
191
|
+
const registration = this.deps.registry.getByRegistryKey(currentContext.registryKey);
|
|
192
|
+
if (!registration?.binding || registration.binding.status !== 'active') {
|
|
193
|
+
throw new Error('managed_room_binding_unavailable');
|
|
194
|
+
}
|
|
195
|
+
const roomContext = this.deps.loadContext
|
|
196
|
+
? await this.deps.loadContext(registration)
|
|
197
|
+
: this.latestContexts.get(currentContext.registryKey);
|
|
198
|
+
if (!roomContext)
|
|
199
|
+
throw new Error('managed_room_prompt_context_unavailable');
|
|
200
|
+
const identity = roomContext.agentsByIdentityId?.[registration.binding.agentIdentityId];
|
|
201
|
+
if (!identity?.permissionPolicy)
|
|
202
|
+
throw new Error('managed_agent_policy_missing');
|
|
203
|
+
const observation = this.deps.observations.getBySource(registration);
|
|
204
|
+
if (!observation?.workspacePath || observation.lifecycleState === 'closed') {
|
|
205
|
+
throw new Error('managed_room_workspace_unavailable');
|
|
206
|
+
}
|
|
207
|
+
const managedAgentPolicy = this.deps.resolveManagedAgentPolicy
|
|
208
|
+
? await this.deps.resolveManagedAgentPolicy({
|
|
209
|
+
registration,
|
|
210
|
+
permissionPolicy: identity.permissionPolicy,
|
|
211
|
+
permissionConfirmation: identity.permissionConfirmation,
|
|
212
|
+
workDir: observation.workspacePath,
|
|
213
|
+
})
|
|
214
|
+
: await resolveManagedAgentPolicy({
|
|
215
|
+
registration,
|
|
216
|
+
permissionPolicy: identity.permissionPolicy,
|
|
217
|
+
permissionConfirmation: identity.permissionConfirmation,
|
|
218
|
+
workDir: observation.workspacePath,
|
|
219
|
+
});
|
|
220
|
+
const prompt = buildRoomSystemPrompt(roomContext, registration, managedAgentPolicy);
|
|
221
|
+
const token = this.deps.issueManagedRoomContext(registration.registryKey, currentContext.causeMessageId);
|
|
222
|
+
if (!/^mrc_[A-Za-z0-9_-]{43}$/.test(token)) {
|
|
223
|
+
throw new Error('managed_room_context_unavailable');
|
|
224
|
+
}
|
|
225
|
+
this.audit(registration, currentContext.causeMessageId, currentContext.rootCauseMessageId, currentContext.hopCount, 'activated', 'conversation_queue_dispatch');
|
|
226
|
+
return {
|
|
227
|
+
systemPrompt: prompt.systemPrompt,
|
|
228
|
+
roomContextRegistryKey: registration.registryKey,
|
|
229
|
+
managedRoomContextToken: token,
|
|
230
|
+
managedAgentPolicy,
|
|
231
|
+
managedRoomPromptSnapshot: prompt.snapshot,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
audit(registration, causeMessageId, rootCauseMessageId, hopCount, outcome, reason) {
|
|
235
|
+
if (!registration.binding)
|
|
236
|
+
return;
|
|
237
|
+
this.deps.audit?.record({
|
|
238
|
+
roomId: registration.binding.roomId,
|
|
239
|
+
bindingId: registration.binding.bindingId,
|
|
240
|
+
causeMessageId,
|
|
241
|
+
rootCauseMessageId,
|
|
242
|
+
hopCount,
|
|
243
|
+
outcome,
|
|
244
|
+
reason,
|
|
245
|
+
at: this.deps.now?.(),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function buildDeliveryText(messages) {
|
|
250
|
+
const blocks = messages.map((message) => {
|
|
251
|
+
const text = message.text?.trim() || '发送了一条消息';
|
|
252
|
+
const attachments = message.attachments.length > 0
|
|
253
|
+
? `\n${message.attachments.map(formatDeliveryAttachment).join('\n')}`
|
|
254
|
+
: '';
|
|
255
|
+
return { speaker: message.author.displayName, body: `${text}${attachments}` };
|
|
256
|
+
});
|
|
257
|
+
return formatDeliveryBlocks(blocks);
|
|
258
|
+
}
|
|
259
|
+
function formatDeliveryAttachment(attachment) {
|
|
260
|
+
const label = `${escapeMarkdownLabel(attachment.name)} · ${formatByteSize(attachment.byteSize)}`;
|
|
261
|
+
return `附件:${label}|${attachment.mimeType}|attachmentId=${attachment.attachmentId}`;
|
|
262
|
+
}
|
|
263
|
+
function escapeMarkdownLabel(value) {
|
|
264
|
+
return String(value || '附件').replace(/[[\]\\]/g, '\\$&').replace(/[\r\n]+/g, ' ');
|
|
265
|
+
}
|
|
266
|
+
function formatByteSize(value) {
|
|
267
|
+
const bytes = Number(value);
|
|
268
|
+
if (!Number.isFinite(bytes) || bytes < 0)
|
|
269
|
+
return `${value} B`;
|
|
270
|
+
if (bytes < 1024)
|
|
271
|
+
return `${bytes} B`;
|
|
272
|
+
const units = ['KB', 'MB', 'GB', 'TB'];
|
|
273
|
+
let size = bytes;
|
|
274
|
+
let unit = -1;
|
|
275
|
+
do {
|
|
276
|
+
size /= 1024;
|
|
277
|
+
unit += 1;
|
|
278
|
+
} while (size >= 1024 && unit < units.length - 1);
|
|
279
|
+
const digits = size >= 100 ? 0 : size >= 10 ? 1 : 2;
|
|
280
|
+
return `${size.toFixed(digits).replace(/\.0+$|(?<=\.[0-9])0+$/, '')} ${units[unit]}`;
|
|
281
|
+
}
|
|
282
|
+
function mergeDeliveryTexts(deliveryTexts) {
|
|
283
|
+
return formatDeliveryBlocks(deliveryTexts.map(parseDeliveryText));
|
|
284
|
+
}
|
|
285
|
+
function parseDeliveryText(deliveryText) {
|
|
286
|
+
const compact = deliveryText.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);
|
|
287
|
+
if (compact)
|
|
288
|
+
return { speaker: compact[1], body: compact[2] };
|
|
289
|
+
// Registries written by earlier local daemons used a three-line header.
|
|
290
|
+
const legacy = deliveryText.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);
|
|
291
|
+
if (legacy)
|
|
292
|
+
return { speaker: legacy[1], body: legacy[2] };
|
|
293
|
+
// A short-lived format used an unbracketed source label before the speaker.
|
|
294
|
+
const labeledLegacy = deliveryText.match(/^群消息\n([^\n]+)\n([\s\S]*)$/);
|
|
295
|
+
if (labeledLegacy)
|
|
296
|
+
return { speaker: labeledLegacy[1], body: labeledLegacy[2] };
|
|
297
|
+
// The first natural Room format used an unadorned speaker on the first line.
|
|
298
|
+
const natural = deliveryText.match(/^([^\n]+)\n([\s\S]*)$/);
|
|
299
|
+
if (natural)
|
|
300
|
+
return { speaker: natural[1], body: natural[2] };
|
|
301
|
+
return { speaker: '群成员', body: deliveryText.trim() };
|
|
302
|
+
}
|
|
303
|
+
function formatDeliveryBlocks(blocks) {
|
|
304
|
+
if (blocks.length === 1) {
|
|
305
|
+
const [block] = blocks;
|
|
306
|
+
return `[群消息] <${block.speaker}>\n${block.body}`;
|
|
307
|
+
}
|
|
308
|
+
return `[群消息]\n${blocks.map((block) => `<${block.speaker}>\n${block.body}`).join('\n\n')}`;
|
|
309
|
+
}
|
|
310
|
+
function buildRoomSystemPrompt(context, registration, managedAgentPolicy) {
|
|
311
|
+
const identity = registration.binding
|
|
312
|
+
? context.agentsByIdentityId?.[registration.binding.agentIdentityId]
|
|
313
|
+
: undefined;
|
|
314
|
+
if (!registration.binding || !identity)
|
|
315
|
+
throw new Error('managed_room_prompt_incomplete');
|
|
316
|
+
return composeManagedRoomPrompt({
|
|
317
|
+
runtimeAdapter: toAgentType(registration.runtimeAdapter),
|
|
318
|
+
agent: {
|
|
319
|
+
displayName: identity.displayName,
|
|
320
|
+
description: identity.description,
|
|
321
|
+
systemPrompt: context.agentSystemPromptsByIdentityId?.[registration.binding.agentIdentityId],
|
|
322
|
+
},
|
|
323
|
+
room: {
|
|
324
|
+
name: context.roomName,
|
|
325
|
+
activationMode: registration.binding.activationMode,
|
|
326
|
+
members: context.members,
|
|
327
|
+
},
|
|
328
|
+
runtimePermissions: {
|
|
329
|
+
requestedPolicy: managedAgentPolicy.requestedPolicy,
|
|
330
|
+
enforcement: managedAgentPolicy.enforcement,
|
|
331
|
+
resolvedWorkDir: managedAgentPolicy.adapterPolicy.workDir,
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
export async function resolveManagedAgentPolicy(input) {
|
|
336
|
+
const agentType = toAgentType(input.registration.runtimeAdapter);
|
|
337
|
+
const command = isAvailableBuiltinAgentType(agentType)
|
|
338
|
+
? resolveBuiltinCommand(agentType)
|
|
339
|
+
: null;
|
|
340
|
+
const customEntry = agentType.startsWith('custom:')
|
|
341
|
+
? loadConfig().customAgents?.[agentType.slice('custom:'.length)]
|
|
342
|
+
: undefined;
|
|
343
|
+
if (customEntry)
|
|
344
|
+
registerCustomRuntimeSecurity(agentType, customEntry);
|
|
345
|
+
const runtimeVersion = command
|
|
346
|
+
? getBuiltinAgentVersion(agentType, command)
|
|
347
|
+
: customEntry?.caps.version;
|
|
348
|
+
const executableIdentity = command
|
|
349
|
+
? resolveManagedAgentExecutableIdentity(command)
|
|
350
|
+
: {
|
|
351
|
+
platform: process.platform,
|
|
352
|
+
isWsl: process.platform === 'linux' &&
|
|
353
|
+
Boolean(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP),
|
|
354
|
+
executableFingerprint: customEntry
|
|
355
|
+
? resolveCustomRuntimeExecutableFingerprint(customEntry)
|
|
356
|
+
: undefined,
|
|
357
|
+
};
|
|
358
|
+
const runtimeInput = {
|
|
359
|
+
agentType,
|
|
360
|
+
runtimeVersion,
|
|
361
|
+
platform: executableIdentity.platform,
|
|
362
|
+
isWsl: executableIdentity.isWsl,
|
|
363
|
+
executableFingerprint: executableIdentity.executableFingerprint,
|
|
364
|
+
requireExecutableFingerprint: true,
|
|
365
|
+
};
|
|
366
|
+
const protection = resolveManagedAgentRuntimeProtection(runtimeInput);
|
|
367
|
+
const confirmationMatches = managedAgentPermissionConfirmationMatches({
|
|
368
|
+
confirmation: input.permissionConfirmation,
|
|
369
|
+
policy: input.permissionPolicy,
|
|
370
|
+
runtimeAdapter: agentType,
|
|
371
|
+
protection,
|
|
372
|
+
});
|
|
373
|
+
const result = await compileManagedAgentPolicy({
|
|
374
|
+
...runtimeInput,
|
|
375
|
+
policy: input.permissionPolicy,
|
|
376
|
+
workDir: input.workDir,
|
|
377
|
+
hostFullAccessConfirmed: confirmationMatches && input.permissionConfirmation?.hostFullAccess,
|
|
378
|
+
limitedProtectionConfirmed: confirmationMatches && input.permissionConfirmation?.limitedProtection,
|
|
379
|
+
});
|
|
380
|
+
if (!result.ok) {
|
|
381
|
+
const unsupported = result.unsupportedCapabilities?.length
|
|
382
|
+
? `:${result.unsupportedCapabilities.join(',')}`
|
|
383
|
+
: '';
|
|
384
|
+
throw new Error(`${result.errorCode}${unsupported}`);
|
|
385
|
+
}
|
|
386
|
+
return result.value;
|
|
387
|
+
}
|
|
388
|
+
export function managedAgentPermissionConfirmationMatches(input) {
|
|
389
|
+
const { confirmation, policy, runtimeAdapter, protection } = input;
|
|
390
|
+
return Boolean(confirmation &&
|
|
391
|
+
confirmation.policyVersion === policy.version &&
|
|
392
|
+
confirmation.runtimeAdapter === runtimeAdapter &&
|
|
393
|
+
confirmation.runtimeVersion === protection.runtimeVersion &&
|
|
394
|
+
confirmation.platform === protection.platform &&
|
|
395
|
+
confirmation.executableFingerprint === protection.executableFingerprint &&
|
|
396
|
+
confirmation.hostFullAccess === (policy.fileAccess === 'host-full') &&
|
|
397
|
+
confirmation.limitedProtection === (protection.level !== 'hard'));
|
|
398
|
+
}
|
|
399
|
+
function toAgentType(runtime) {
|
|
400
|
+
if (runtime === 'claude_code')
|
|
401
|
+
return 'claude';
|
|
402
|
+
return runtime;
|
|
403
|
+
}
|
|
404
|
+
function compareSequence(left, right) {
|
|
405
|
+
const a = BigInt(left);
|
|
406
|
+
const b = BigInt(right);
|
|
407
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
408
|
+
}
|
|
@@ -1 +1,156 @@
|
|
|
1
|
-
|
|
1
|
+
// @arch docs/features/network-rooms/implementation-plan.md#八、邀请链接作为人和-agent-共同入口
|
|
2
|
+
// @test src/__tests__/room-invite-bootstrap.test.ts
|
|
3
|
+
const OFFICIAL_BOOTSTRAP_URL = 'https://shennian.net/api/room-invites/agent-bootstrap';
|
|
4
|
+
const EXPECTED_ACTIONS = [
|
|
5
|
+
'room.create',
|
|
6
|
+
'room.join',
|
|
7
|
+
'room.read',
|
|
8
|
+
'room.send',
|
|
9
|
+
'room.mode',
|
|
10
|
+
'room.status',
|
|
11
|
+
];
|
|
12
|
+
const EXPECTED_RUNTIMES = ['codex', 'claude_code', 'workbuddy'];
|
|
13
|
+
const EXPECTED_INTEGRATION_COMMANDS = {
|
|
14
|
+
codex: {
|
|
15
|
+
install: 'shennian integration codex install --confirm-user-action',
|
|
16
|
+
update: 'shennian integration codex update --confirm-user-action',
|
|
17
|
+
},
|
|
18
|
+
claude_code: {
|
|
19
|
+
install: 'shennian integration claude-code install --confirm-user-action',
|
|
20
|
+
update: 'shennian integration claude-code update --confirm-user-action',
|
|
21
|
+
},
|
|
22
|
+
workbuddy: {
|
|
23
|
+
install: 'shennian integration workbuddy install --confirm-user-action',
|
|
24
|
+
update: 'shennian integration workbuddy update --confirm-user-action',
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
export class RoomInviteBootstrapError extends Error {
|
|
28
|
+
code;
|
|
29
|
+
constructor(code, message) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.code = code;
|
|
32
|
+
this.name = 'RoomInviteBootstrapError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export async function fetchTrustedRoomAgentBootstrap(fetchImpl = fetch) {
|
|
36
|
+
let response;
|
|
37
|
+
try {
|
|
38
|
+
response = await fetchImpl(OFFICIAL_BOOTSTRAP_URL, {
|
|
39
|
+
headers: { accept: 'application/json' },
|
|
40
|
+
redirect: 'error',
|
|
41
|
+
signal: AbortSignal.timeout(10_000),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
throw new RoomInviteBootstrapError('room_bootstrap_unavailable', 'Official Shennian Room setup information is unavailable.');
|
|
46
|
+
}
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
throw new RoomInviteBootstrapError('room_bootstrap_unavailable', 'Official Shennian Room setup information is unavailable.');
|
|
49
|
+
}
|
|
50
|
+
let value;
|
|
51
|
+
try {
|
|
52
|
+
value = await response.json();
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
throw invalidBootstrap();
|
|
56
|
+
}
|
|
57
|
+
return validateTrustedRoomAgentBootstrap(value);
|
|
58
|
+
}
|
|
59
|
+
export function validateTrustedRoomAgentBootstrap(value) {
|
|
60
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
61
|
+
throw invalidBootstrap();
|
|
62
|
+
const root = value;
|
|
63
|
+
const install = readObject(root.install);
|
|
64
|
+
const roomTool = readObject(root.roomTool);
|
|
65
|
+
const integrationCommands = roomTool.integrationCommands === undefined
|
|
66
|
+
? EXPECTED_INTEGRATION_COMMANDS
|
|
67
|
+
: readObject(roomTool.integrationCommands);
|
|
68
|
+
if (root.schemaVersion !== 1 ||
|
|
69
|
+
root.product !== 'shennian' ||
|
|
70
|
+
root.inviteKind !== 'room' ||
|
|
71
|
+
install.documentationUrl !== 'https://shennian.net/agent-install.md' ||
|
|
72
|
+
install.npmPackage !== 'shennian' ||
|
|
73
|
+
install.distTag !== 'latest' ||
|
|
74
|
+
install.installCommand !== 'npm install -g shennian@latest' ||
|
|
75
|
+
install.upgradeCommand !== 'shennian upgrade' ||
|
|
76
|
+
typeof install.minimumCliVersion !== 'string' ||
|
|
77
|
+
!/^\d+\.\d+\.\d+$/.test(install.minimumCliVersion) ||
|
|
78
|
+
roomTool.schemaVersion !== 1 ||
|
|
79
|
+
!sameStrings(roomTool.actions, EXPECTED_ACTIONS) ||
|
|
80
|
+
!sameStrings(roomTool.supportedRuntimes, EXPECTED_RUNTIMES) ||
|
|
81
|
+
!sameIntegrationCommands(integrationCommands)) {
|
|
82
|
+
throw invalidBootstrap();
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
documentationUrl: 'https://shennian.net/agent-install.md',
|
|
86
|
+
installCommand: 'npm install -g shennian@latest',
|
|
87
|
+
upgradeCommand: 'shennian upgrade',
|
|
88
|
+
minimumCliVersion: install.minimumCliVersion,
|
|
89
|
+
actions: EXPECTED_ACTIONS,
|
|
90
|
+
supportedRuntimes: EXPECTED_RUNTIMES,
|
|
91
|
+
integrationCommands: EXPECTED_INTEGRATION_COMMANDS,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function sameIntegrationCommands(value) {
|
|
95
|
+
if (Object.keys(value).sort().join(',') !== [...EXPECTED_RUNTIMES].sort().join(','))
|
|
96
|
+
return false;
|
|
97
|
+
return EXPECTED_RUNTIMES.every((runtime) => {
|
|
98
|
+
const commands = value[runtime];
|
|
99
|
+
if (!commands || typeof commands !== 'object' || Array.isArray(commands))
|
|
100
|
+
return false;
|
|
101
|
+
const object = commands;
|
|
102
|
+
const expected = EXPECTED_INTEGRATION_COMMANDS[runtime];
|
|
103
|
+
return (Object.keys(object).sort().join(',') === 'install,update' &&
|
|
104
|
+
object.install === expected.install &&
|
|
105
|
+
object.update === expected.update);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
export function resolveRoomClientPreparation(currentVersion, bootstrap) {
|
|
109
|
+
if (currentVersion === null) {
|
|
110
|
+
return {
|
|
111
|
+
status: 'install_required',
|
|
112
|
+
documentationUrl: bootstrap.documentationUrl,
|
|
113
|
+
command: bootstrap.installCommand,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const current = parseVersion(currentVersion);
|
|
117
|
+
const minimum = parseVersion(bootstrap.minimumCliVersion);
|
|
118
|
+
if (!current || !minimum) {
|
|
119
|
+
throw new RoomInviteBootstrapError('invalid_local_cli_version', 'The installed Shennian CLI version could not be verified.');
|
|
120
|
+
}
|
|
121
|
+
if (compareVersion(current, minimum) < 0) {
|
|
122
|
+
return {
|
|
123
|
+
status: 'upgrade_required',
|
|
124
|
+
documentationUrl: bootstrap.documentationUrl,
|
|
125
|
+
command: bootstrap.upgradeCommand,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return { status: 'ready' };
|
|
129
|
+
}
|
|
130
|
+
function readObject(value) {
|
|
131
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
132
|
+
throw invalidBootstrap();
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
function sameStrings(value, expected) {
|
|
136
|
+
return (Array.isArray(value) &&
|
|
137
|
+
value.length === expected.length &&
|
|
138
|
+
value.every((entry, index) => entry === expected[index]));
|
|
139
|
+
}
|
|
140
|
+
function parseVersion(value) {
|
|
141
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/.exec(value.trim());
|
|
142
|
+
if (!match)
|
|
143
|
+
return null;
|
|
144
|
+
const parts = [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
145
|
+
return parts.every(Number.isSafeInteger) ? parts : null;
|
|
146
|
+
}
|
|
147
|
+
function compareVersion(left, right) {
|
|
148
|
+
for (let index = 0; index < 3; index += 1) {
|
|
149
|
+
if (left[index] !== right[index])
|
|
150
|
+
return left[index] - right[index];
|
|
151
|
+
}
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
154
|
+
function invalidBootstrap() {
|
|
155
|
+
return new RoomInviteBootstrapError('invalid_room_bootstrap', 'Official Shennian Room setup information failed validation.');
|
|
156
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RoomSessionRegistry } from './session-registry.js';
|
|
2
2
|
export declare const MANAGED_ROOM_CONTEXT_ENV = "SHENNIAN_MANAGED_ROOM_CONTEXT";
|
|
3
|
-
export type ManagedRoomAction = 'status' | 'read' | 'send' | 'attachment.get';
|
|
3
|
+
export type ManagedRoomAction = 'status' | 'read' | 'send' | 'attachment.get' | 'pause';
|
|
4
4
|
type ManagedRoomGrant = {
|
|
5
5
|
tokenHash: string;
|
|
6
6
|
registryKey: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import g from"node:crypto";const h="SHENNIAN_MANAGED_ROOM_CONTEXT";class i extends Error{code;constructor(e,t){super(t),this.code=e,this.name="ManagedRoomContextError"}}class m{registry;now;grants=new Map;constructor(e,t=Date.now){this.registry=e,this.now=t}issue(e){const t=this.registry.getByRegistryKey(e.registryKey);if(!t?.binding||t.lifecycleState==="closed")throw new i("managed_room_context_unavailable","Managed Room context is unavailable.");if(t.binding.status!=="active"&&t.binding.status!=="paused")throw new i("managed_room_binding_unavailable","Managed Room binding is unavailable.");if(!t.binding.agentMembershipId)throw new i("managed_room_membership_unavailable","Managed Room membership is unavailable.");const n=e.machineCredential.trim();if(!n)throw new i("machine_not_authorized","This machine is not authorized yet.");const a=`mrc_${g.randomBytes(32).toString("base64url")}`,o=s(a);return this.grants.set(o,{tokenHash:o,registryKey:t.registryKey,registrationEpoch:t.registrationEpoch,credentialHash:s(n),allowedActions:new Set(e.allowedActions??["status","read","send","attachment.get"]),activationCauseMessageId:e.activationCauseMessageId,expiresAt:this.now()+Math.min(Math.max(e.ttlMs??10*6e4,1e3),30*6e4)}),this.prune(),a}resolve(e){const t=s(e.token),n=this.grants.get(t);if(!n||n.expiresAt<=this.now())throw this.grants.delete(t),new i("managed_room_context_expired","Managed Room context expired.");if(!d(n.credentialHash,s(e.machineCredential)))throw new i("managed_room_context_invalid","Managed Room context is invalid.");if(!n.allowedActions.has(e.action))throw new i("managed_room_action_forbidden","This action is not allowed in the managed Room context.");const a=this.registry.getByRegistryKey(n.registryKey),o=a?.binding;if(!a||!o||a.registrationEpoch!==n.registrationEpoch||a.lifecycleState==="closed"||o.status!=="active"&&o.status!=="paused"||!o.agentMembershipId)throw this.grants.delete(t),new i("managed_room_context_revoked","Managed Room context was revoked.");return{grant:n,registration:a,binding:o}}revokeToken(e){this.grants.delete(s(e))}revokeSession(e){for(const[t,n]of this.grants)n.registryKey===e&&this.grants.delete(t)}prune(){const e=this.now();for(const[t,n]of this.grants)n.expiresAt<=e&&this.grants.delete(t)}}function s(r){return g.createHash("sha256").update(r,"utf8").digest("hex")}function d(r,e){return g.timingSafeEqual(Buffer.from(r,"hex"),Buffer.from(e,"hex"))}export{h as MANAGED_ROOM_CONTEXT_ENV,m as ManagedRoomContextAuthority,i as ManagedRoomContextError};
|
|
1
|
+
import g from"node:crypto";const h="SHENNIAN_MANAGED_ROOM_CONTEXT";class i extends Error{code;constructor(e,t){super(t),this.code=e,this.name="ManagedRoomContextError"}}class m{registry;now;grants=new Map;constructor(e,t=Date.now){this.registry=e,this.now=t}issue(e){const t=this.registry.getByRegistryKey(e.registryKey);if(!t?.binding||t.lifecycleState==="closed")throw new i("managed_room_context_unavailable","Managed Room context is unavailable.");if(t.binding.status!=="active"&&t.binding.status!=="paused")throw new i("managed_room_binding_unavailable","Managed Room binding is unavailable.");if(!t.binding.agentMembershipId)throw new i("managed_room_membership_unavailable","Managed Room membership is unavailable.");const n=e.machineCredential.trim();if(!n)throw new i("machine_not_authorized","This machine is not authorized yet.");const a=`mrc_${g.randomBytes(32).toString("base64url")}`,o=s(a);return this.grants.set(o,{tokenHash:o,registryKey:t.registryKey,registrationEpoch:t.registrationEpoch,credentialHash:s(n),allowedActions:new Set(e.allowedActions??["status","read","send","attachment.get","pause"]),activationCauseMessageId:e.activationCauseMessageId,expiresAt:this.now()+Math.min(Math.max(e.ttlMs??10*6e4,1e3),30*6e4)}),this.prune(),a}resolve(e){const t=s(e.token),n=this.grants.get(t);if(!n||n.expiresAt<=this.now())throw this.grants.delete(t),new i("managed_room_context_expired","Managed Room context expired.");if(!d(n.credentialHash,s(e.machineCredential)))throw new i("managed_room_context_invalid","Managed Room context is invalid.");if(!n.allowedActions.has(e.action))throw new i("managed_room_action_forbidden","This action is not allowed in the managed Room context.");const a=this.registry.getByRegistryKey(n.registryKey),o=a?.binding;if(!a||!o||a.registrationEpoch!==n.registrationEpoch||a.lifecycleState==="closed"||o.status!=="active"&&o.status!=="paused"||!o.agentMembershipId)throw this.grants.delete(t),new i("managed_room_context_revoked","Managed Room context was revoked.");return{grant:n,registration:a,binding:o}}revokeToken(e){this.grants.delete(s(e))}revokeSession(e){for(const[t,n]of this.grants)n.registryKey===e&&this.grants.delete(t)}prune(){const e=this.now();for(const[t,n]of this.grants)n.expiresAt<=e&&this.grants.delete(t)}}function s(r){return g.createHash("sha256").update(r,"utf8").digest("hex")}function d(r,e){return g.timingSafeEqual(Buffer.from(r,"hex"),Buffer.from(e,"hex"))}export{h as MANAGED_ROOM_CONTEXT_ENV,m as ManagedRoomContextAuthority,i as ManagedRoomContextError};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ManagedAgentEnforcement, ManagedAgentPermissionPolicy, AgentType, RoomActivationMode } from '@shennian/wire';
|
|
2
|
-
export declare const MANAGED_ROOM_POLICY_VERSION = "managed-room-surface-
|
|
2
|
+
export declare const MANAGED_ROOM_POLICY_VERSION = "managed-room-surface-v4";
|
|
3
3
|
export type ManagedRoomPromptInput = {
|
|
4
4
|
agent: {
|
|
5
5
|
displayName: string;
|