shennian 0.3.86 → 0.4.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/README.md +3 -2
- package/dist/bin/shennian.js +1 -1
- package/dist/publish-build-manifest.json +32 -27
- package/dist/src/agents/adapter.js +1 -19
- package/dist/src/agents/claude-managed-permissions.js +1 -248
- package/dist/src/agents/claude.js +8 -393
- package/dist/src/agents/codex-managed-permissions.js +1 -294
- package/dist/src/agents/codex-managed-room-tools.js +1 -249
- package/dist/src/agents/codex-utils.js +7 -200
- package/dist/src/agents/codex.d.ts +7 -1
- package/dist/src/agents/codex.js +17 -1352
- package/dist/src/agents/config-status.js +1 -226
- package/dist/src/agents/cursor-managed-permissions.js +2 -299
- package/dist/src/agents/cursor.js +1 -314
- package/dist/src/agents/custom.js +4 -418
- package/dist/src/agents/external-channel-instructions.js +11 -94
- package/dist/src/agents/gemini.js +1 -180
- package/dist/src/agents/manager.js +18 -250
- package/dist/src/agents/model-registry/cache.js +1 -37
- package/dist/src/agents/model-registry/discovery.js +2 -188
- package/dist/src/agents/model-registry/parsers.js +4 -521
- package/dist/src/agents/model-registry/runner.js +1 -30
- package/dist/src/agents/model-registry/service.js +1 -115
- package/dist/src/agents/model-registry/types.js +1 -8
- package/dist/src/agents/model-registry.js +1 -19
- package/dist/src/agents/native-capabilities.js +1 -237
- package/dist/src/agents/openclaw.js +2 -293
- package/dist/src/agents/opencode-managed-permissions.js +2 -187
- package/dist/src/agents/opencode.js +1 -298
- package/dist/src/agents/platform-instructions.js +17 -46
- package/dist/src/agents/runtime-compatibility.js +1 -44
- package/dist/src/agents/workbuddy-managed-permissions.js +2 -329
- package/dist/src/agents/workbuddy.js +3 -444
- package/dist/src/commands/logout.d.ts +3 -0
- package/dist/src/commands/logout.js +1 -0
- package/dist/src/daemon-ipc/paths.js +1 -1
- package/dist/src/fs/boundary.js +1 -185
- package/dist/src/fs/handler.js +1 -130
- package/dist/src/fs/security.js +1 -32
- package/dist/src/fs/text-decoder.js +1 -110
- package/dist/src/index.js +3 -587
- package/dist/src/manager/prompt.js +29 -34
- package/dist/src/manager/registry.js +1 -317
- package/dist/src/manager/retired-external-channel-compat.js +1 -40
- package/dist/src/manager/runtime.js +23 -1514
- package/dist/src/native-fusion/codex-parser.d.ts +3 -1
- package/dist/src/native-fusion/codex-parser.js +4 -660
- package/dist/src/native-fusion/codex-user-input.js +5 -102
- package/dist/src/native-fusion/config.js +1 -5
- package/dist/src/native-fusion/file-watcher.js +1 -191
- package/dist/src/native-fusion/local-repository.js +2 -569
- package/dist/src/native-fusion/opencode-parser.js +3 -130
- package/dist/src/native-fusion/parser-common.d.ts +6 -1
- package/dist/src/native-fusion/parser-common.js +4 -4
- package/dist/src/native-fusion/parsers.d.ts +4 -2
- package/dist/src/native-fusion/parsers.js +3 -140
- package/dist/src/native-fusion/service.js +2 -739
- package/dist/src/native-fusion/session-header.js +2 -101
- package/dist/src/native-fusion/state.js +2 -104
- package/dist/src/native-fusion/types.js +1 -1
- package/dist/src/personal-sync/machine-authority.js +1 -34
- package/dist/src/room/activation-audit.js +2 -45
- package/dist/src/room/activation-policy.js +1 -30
- package/dist/src/room/activation-service.js +8 -408
- package/dist/src/room/invite-bootstrap.js +1 -156
- package/dist/src/room/managed-prompt-composer.d.ts +1 -1
- package/dist/src/room/managed-prompt-composer.js +17 -16
- package/dist/src/room/managed-room-binding.js +1 -167
- package/dist/src/room/managed-room-ipc.d.ts +4 -0
- package/dist/src/room/managed-room-ipc.js +2 -2
- package/dist/src/room/mirror-sync-service.d.ts +1 -0
- package/dist/src/room/mirror-sync-service.js +1 -183
- package/dist/src/room/room-binding-client.js +1 -151
- package/dist/src/session/archive-zip.js +1 -220
- package/dist/src/session/deletion-sync-state.js +2 -136
- package/dist/src/session/handlers/agent-capabilities.js +1 -14
- package/dist/src/session/handlers/agent-config.js +1 -150
- package/dist/src/session/handlers/agents.js +1 -55
- package/dist/src/session/handlers/chat.js +2 -1030
- package/dist/src/session/handlers/control.js +1 -61
- package/dist/src/session/handlers/fs.js +1 -594
- package/dist/src/session/handlers/message-status.js +1 -15
- package/dist/src/session/handlers/remote-access.js +1 -43
- package/dist/src/session/handlers/room-managed.js +1 -101
- package/dist/src/session/handlers/session-refresh.js +1 -35
- package/dist/src/session/handlers/title.js +1 -79
- package/dist/src/session/handlers/tool-detail.js +1 -149
- package/dist/src/session/handlers/voice-window.js +1 -62
- package/dist/src/session/history-subscriptions.js +1 -470
- package/dist/src/session/manager.d.ts +1 -0
- package/dist/src/session/manager.js +1 -658
- package/dist/src/session/native-cleanup-outbox.js +2 -287
- package/dist/src/session/native-session-lifecycle.js +6 -327
- package/dist/src/session/queue.d.ts +8 -0
- package/dist/src/session/queue.js +6 -6
- package/dist/src/upgrade/scheduler.js +1 -126
- package/node_modules/@shennian/wire/dist/agent-workspace.js +2 -2
- package/package.json +1 -1
|
@@ -1,408 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
+
import{isAvailableBuiltinAgentType as A}from"@shennian/wire";import{getBuiltinAgentVersion as k,resolveBuiltinCommand as P}from"../agents/command-spec.js";import{registerCustomRuntimeSecurity as S,resolveCustomRuntimeExecutableFingerprint as K}from"../agents/custom-managed-runtime.js";import{loadConfig as B}from"../config/index.js";import{resolveManagedAgentExecutableIdentity as $}from"../security/managed-agent-executable.js";import{compileManagedAgentPolicy as x}from"../security/managed-agent-policy.js";import{resolveManagedAgentRuntimeProtection as q}from"../security/managed-agent-runtime.js";import{selectRoomActivationTargets as T}from"./activation-policy.js";import{composeManagedRoomPrompt as D}from"./managed-prompt-composer.js";const R=4,E=6e4,W=6,N=5*6e4;class oe{deps;latestContexts=new Map;constructor(e){this.deps=e}async processMessages(e,s,t,a={roomName:"\u5F53\u524D\u7FA4\u804A"}){const d=[],c=[],g=new Map,m=new Set,u=[...s].sort((i,r)=>G(i.sequence,r.sequence));for(const i of t){const r=this.deps.registry.getByRegistryKey(i.registryKey);!r?.binding||r.binding.roomId!==e||r.binding.status!=="active"&&r.binding.status!=="paused"&&r.binding.activationQueue.length}for(const i of t){const r=this.deps.registry.getByRegistryKey(i.registryKey);if(!r?.binding||r.binding.roomId!==e||r.binding.activationQueue.length===0&&!r.binding.activeActivation||(m.add(r.registryKey),r.binding.status!=="active"||r.binding.activationMode==="paused")||r.binding.activationQueue.length===0)continue;const p=this.deps.observations.getBySource(r);if(!p?.workspacePath||p.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");this.latestContexts.set(r.registryKey,a);for(const o of r.binding.activationQueue)d.push({registration:r,input:{params:{sessionId:r.nianSessionId,text:o.deliveryText??"\u7FA4\u804A\u4E2D\u6709\u4E00\u6761\u5F85\u5904\u7406\u6D88\u606F\uFF0C\u8BF7\u7ED3\u5408\u5F53\u524D\u7FA4\u804A\u4E0A\u4E0B\u6587\u5904\u7406\u3002",agentType:f(r.runtimeAdapter),sessionMode:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:p.workspacePath,agentSessionId:r.sourceSessionKey,queueMessageId:`room-activation-${r.binding.bindingId}-${o.causeMessageId}`,clientMessageId:`room-activation-${r.binding.bindingId}-${o.causeMessageId}`},context:{registryKey:r.registryKey,causeMessageId:o.causeMessageId,rootCauseMessageId:o.rootCauseMessageId,sequence:o.sequence,hopCount:o.hopCount}}})}for(const i of u){const r=new Set(T(i,t).map(p=>p.registration.registryKey));for(const p of t){const o=this.deps.registry.getByRegistryKey(p.registryKey);if(!o?.binding||o.binding.roomId!==e||o.binding.status!=="active"&&o.binding.status!=="paused"||BigInt(i.sequence)<=BigInt(o.binding.lastActivatedSequence))continue;const l=g.get(o.registryKey)??[...o.binding.mentionContext??[]];if(g.set(o.registryKey,l),r.has(o.registryKey)){const y=(i.activationCause?.hopCount??0)+(i.author.principalType==="agent"?1:0),I=i.activationCause?.rootCauseMessageId??i.id;if(y>R)this.audit(o,i.id,I,y,"suppressed","max_hops");else{const h=this.deps.registry.claimActivation({registryKey:o.registryKey,windowMs:E,maxActivations:W,pauseMs:N,now:this.deps.now?.()});if(!h.allowed){h.newlyPaused&&this.audit(o,i.id,I,y,"suppressed","rate_limited"),c.push({registryKey:o.registryKey,sequence:i.sequence});continue}const b=this.deps.observations.getBySource(o);if(!b?.workspacePath||b.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const v=w([i]),M=o.binding.activationMode==="mention_only"?L([...l.map(_=>_.deliveryText),v]):v;this.latestContexts.set(o.registryKey,a),d.push({registration:o,input:{params:{sessionId:o.nianSessionId,text:M,agentType:f(o.runtimeAdapter),sessionMode:a.agentsByIdentityId?.[o.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:a.agentsByIdentityId?.[o.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:a.agentsByIdentityId?.[o.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:a.agentsByIdentityId?.[o.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:b.workspacePath,agentSessionId:o.sourceSessionKey,queueMessageId:`room-activation-${o.binding.bindingId}-${i.id}`,clientMessageId:`room-activation-${o.binding.bindingId}-${i.id}`},context:{registryKey:o.registryKey,causeMessageId:i.id,rootCauseMessageId:I,sequence:i.sequence,hopCount:y}}}),o.binding.activationMode==="mention_only"&&g.set(o.registryKey,[])}}else o.binding.activationMode==="mention_only"&&i.kind!=="system"&&i.author.membershipId!==o.binding.agentMembershipId&&(l.some(y=>y.sequence===i.sequence)||l.push({sequence:i.sequence,deliveryText:w([i])}));c.push({registryKey:o.registryKey,sequence:i.sequence})}}await this.deps.enqueueConversationBatch(d.map(i=>i.input));for(const i of m)this.deps.registry.retireLegacyActivationState(i,this.deps.now?.()??new Date);for(const[i,r]of g)this.deps.registry.replaceMentionContext(i,r,this.deps.now?.()??new Date);for(const i of d)this.audit(i.registration,i.input.context.causeMessageId,i.input.context.rootCauseMessageId,i.input.context.hopCount,"queued","conversation_queue");for(const i of c)this.deps.registry.advanceCursor({registryKey:i.registryKey,cursor:"activated",sequence:i.sequence})}async prepareConversationTurn(e){const s=e.at(-1);if(!s)throw new Error("managed_room_queue_context_missing");const t=this.deps.registry.getByRegistryKey(s.registryKey);if(!t?.binding||t.binding.status!=="active")throw new Error("managed_room_binding_unavailable");const a=this.deps.loadContext?await this.deps.loadContext(t):this.latestContexts.get(s.registryKey);if(!a)throw new Error("managed_room_prompt_context_unavailable");const d=a.agentsByIdentityId?.[t.binding.agentIdentityId];if(!d?.permissionPolicy)throw new Error("managed_agent_policy_missing");const c=this.deps.observations.getBySource(t);if(!c?.workspacePath||c.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const g=this.deps.resolveManagedAgentPolicy?await this.deps.resolveManagedAgentPolicy({registration:t,permissionPolicy:d.permissionPolicy,permissionConfirmation:d.permissionConfirmation,workDir:c.workspacePath}):await j({registration:t,permissionPolicy:d.permissionPolicy,permissionConfirmation:d.permissionConfirmation,workDir:c.workspacePath}),m=Q(a,t,g),u=this.deps.issueManagedRoomContext(t.registryKey,s.causeMessageId);if(!/^mrc_[A-Za-z0-9_-]{43}$/.test(u))throw new Error("managed_room_context_unavailable");return this.audit(t,s.causeMessageId,s.rootCauseMessageId,s.hopCount,"activated","conversation_queue_dispatch"),{systemPrompt:m.systemPrompt,roomContextRegistryKey:t.registryKey,managedRoomContextToken:u,managedAgentPolicy:g,managedRoomPromptSnapshot:m.snapshot}}audit(e,s,t,a,d,c){e.binding&&this.deps.audit?.record({roomId:e.binding.roomId,bindingId:e.binding.bindingId,causeMessageId:s,rootCauseMessageId:t,hopCount:a,outcome:d,reason:c,at:this.deps.now?.()})}}function w(n){const e=n.map(s=>{const t=s.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",a=s.attachments.length>0?`
|
|
2
|
+
${s.attachments.map(F).join(`
|
|
3
|
+
`)}`:"";return{speaker:s.author.displayName,body:`${t}${a}`}});return C(e)}function F(n){return`\u9644\u4EF6\uFF1A${`${O(n.name)} \xB7 ${V(n.byteSize)}`}\uFF5C${n.mimeType}\uFF5CattachmentId=${n.attachmentId}`}function O(n){return String(n||"\u9644\u4EF6").replace(/[[\]\\]/g,"\\$&").replace(/[\r\n]+/g," ")}function V(n){const e=Number(n);if(!Number.isFinite(e)||e<0)return`${n} B`;if(e<1024)return`${e} B`;const s=["KB","MB","GB","TB"];let t=e,a=-1;do t/=1024,a+=1;while(t>=1024&&a<s.length-1);const d=t>=100?0:t>=10?1:2;return`${t.toFixed(d).replace(/\.0+$|(?<=\.[0-9])0+$/,"")} ${s[a]}`}function L(n){return C(n.map(z))}function z(n){const e=n.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);if(e)return{speaker:e[1],body:e[2]};const s=n.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);if(s)return{speaker:s[1],body:s[2]};const t=n.match(/^群消息\n([^\n]+)\n([\s\S]*)$/);if(t)return{speaker:t[1],body:t[2]};const a=n.match(/^([^\n]+)\n([\s\S]*)$/);return a?{speaker:a[1],body:a[2]}:{speaker:"\u7FA4\u6210\u5458",body:n.trim()}}function C(n){if(n.length===1){const[e]=n;return`[\u7FA4\u6D88\u606F] <${e.speaker}>
|
|
4
|
+
${e.body}`}return`[\u7FA4\u6D88\u606F]
|
|
5
|
+
${n.map(e=>`<${e.speaker}>
|
|
6
|
+
${e.body}`).join(`
|
|
7
|
+
|
|
8
|
+
`)}`}function Q(n,e,s){const t=e.binding?n.agentsByIdentityId?.[e.binding.agentIdentityId]:void 0;if(!e.binding||!t)throw new Error("managed_room_prompt_incomplete");return D({runtimeAdapter:f(e.runtimeAdapter),agent:{displayName:t.displayName,description:t.description,systemPrompt:n.agentSystemPromptsByIdentityId?.[e.binding.agentIdentityId]},room:{name:n.roomName,activationMode:e.binding.activationMode,members:n.members},runtimePermissions:{requestedPolicy:s.requestedPolicy,enforcement:s.enforcement,resolvedWorkDir:s.adapterPolicy.workDir}})}async function j(n){const e=f(n.registration.runtimeAdapter),s=A(e)?P(e):null,t=e.startsWith("custom:")?B().customAgents?.[e.slice(7)]:void 0;t&&S(e,t);const a=s?k(e,s):t?.caps.version,d=s?$(s):{platform:process.platform,isWsl:process.platform==="linux"&&!!(process.env.WSL_DISTRO_NAME||process.env.WSL_INTEROP),executableFingerprint:t?K(t):void 0},c={agentType:e,runtimeVersion:a,platform:d.platform,isWsl:d.isWsl,executableFingerprint:d.executableFingerprint,requireExecutableFingerprint:!0},g=q(c),m=X({confirmation:n.permissionConfirmation,policy:n.permissionPolicy,runtimeAdapter:e,protection:g}),u=await x({...c,policy:n.permissionPolicy,workDir:n.workDir,hostFullAccessConfirmed:m&&n.permissionConfirmation?.hostFullAccess,limitedProtectionConfirmed:m&&n.permissionConfirmation?.limitedProtection});if(!u.ok){const i=u.unsupportedCapabilities?.length?`:${u.unsupportedCapabilities.join(",")}`:"";throw new Error(`${u.errorCode}${i}`)}return u.value}function X(n){const{confirmation:e,policy:s,runtimeAdapter:t,protection:a}=n;return!!(e&&e.policyVersion===s.version&&e.runtimeAdapter===t&&e.runtimeVersion===a.runtimeVersion&&e.platform===a.platform&&e.executableFingerprint===a.executableFingerprint&&e.hostFullAccess===(s.fileAccess==="host-full")&&e.limitedProtection===(a.level!=="hard"))}function f(n){return n==="claude_code"?"claude":n}function G(n,e){const s=BigInt(n),t=BigInt(e);return s<t?-1:s>t?1:0}export{oe as RoomActivationService,X as managedAgentPermissionConfirmationMatches,j as resolveManagedAgentPolicy};
|
|
@@ -1,156 +1 @@
|
|
|
1
|
-
|
|
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
|
+
const p="https://shennian.net/api/room-invites/agent-bootstrap",d=["room.create","room.join","room.read","room.send","room.mode","room.status"],r=["codex","claude_code","workbuddy"],m={codex:{install:"shennian integration codex install --confirm-user-action",update:"shennian integration codex update --confirm-user-action"},claude_code:{install:"shennian integration claude-code install --confirm-user-action",update:"shennian integration claude-code update --confirm-user-action"},workbuddy:{install:"shennian integration workbuddy install --confirm-user-action",update:"shennian integration workbuddy update --confirm-user-action"}};class a extends Error{code;constructor(t,n){super(n),this.code=t,this.name="RoomInviteBootstrapError"}}async function y(o=fetch){let t;try{t=await o(p,{headers:{accept:"application/json"},redirect:"error",signal:AbortSignal.timeout(1e4)})}catch{throw new a("room_bootstrap_unavailable","Official Shennian Room setup information is unavailable.")}if(!t.ok)throw new a("room_bootstrap_unavailable","Official Shennian Room setup information is unavailable.");let n;try{n=await t.json()}catch{throw s()}return f(n)}function f(o){if(!o||typeof o!="object"||Array.isArray(o))throw s();const t=o,n=c(t.install),e=c(t.roomTool),i=e.integrationCommands===void 0?m:c(e.integrationCommands);if(t.schemaVersion!==1||t.product!=="shennian"||t.inviteKind!=="room"||n.documentationUrl!=="https://shennian.net/agent-install.md"||n.npmPackage!=="shennian"||n.distTag!=="latest"||n.installCommand!=="npm install -g shennian@latest"||n.upgradeCommand!=="shennian upgrade"||typeof n.minimumCliVersion!="string"||!/^\d+\.\d+\.\d+$/.test(n.minimumCliVersion)||e.schemaVersion!==1||!l(e.actions,d)||!l(e.supportedRuntimes,r)||!h(i))throw s();return{documentationUrl:"https://shennian.net/agent-install.md",installCommand:"npm install -g shennian@latest",upgradeCommand:"shennian upgrade",minimumCliVersion:n.minimumCliVersion,actions:d,supportedRuntimes:r,integrationCommands:m}}function h(o){return Object.keys(o).sort().join(",")!==[...r].sort().join(",")?!1:r.every(t=>{const n=o[t];if(!n||typeof n!="object"||Array.isArray(n))return!1;const e=n,i=m[t];return Object.keys(e).sort().join(",")==="install,update"&&e.install===i.install&&e.update===i.update})}function C(o,t){if(o===null)return{status:"install_required",documentationUrl:t.documentationUrl,command:t.installCommand};const n=u(o),e=u(t.minimumCliVersion);if(!n||!e)throw new a("invalid_local_cli_version","The installed Shennian CLI version could not be verified.");return g(n,e)<0?{status:"upgrade_required",documentationUrl:t.documentationUrl,command:t.upgradeCommand}:{status:"ready"}}function c(o){if(!o||typeof o!="object"||Array.isArray(o))throw s();return o}function l(o,t){return Array.isArray(o)&&o.length===t.length&&o.every((n,e)=>n===t[e])}function u(o){const t=/^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/.exec(o.trim());if(!t)return null;const n=[Number(t[1]),Number(t[2]),Number(t[3])];return n.every(Number.isSafeInteger)?n:null}function g(o,t){for(let n=0;n<3;n+=1)if(o[n]!==t[n])return o[n]-t[n];return 0}function s(){return new a("invalid_room_bootstrap","Official Shennian Room setup information failed validation.")}export{a as RoomInviteBootstrapError,y as fetchTrustedRoomAgentBootstrap,C as resolveRoomClientPreparation,f as validateTrustedRoomAgentBootstrap};
|
|
@@ -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-v5";
|
|
3
3
|
export type ManagedRoomPromptInput = {
|
|
4
4
|
agent: {
|
|
5
5
|
displayName: string;
|