shennian 0.3.84 → 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 +30 -30
- 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,2 +1,101 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// @arch docs/architecture/cli/native-session-fusion.md#5-持久同步纪元未知文件分类5-分钟元数据兜底与轮次边界
|
|
2
|
+
// @test src/__tests__/native-fusion-parsers.test.ts
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { readTimestamp, safeParse } from './parser-common.js';
|
|
5
|
+
const MAX_HEADER_BYTES = 1024 * 1024;
|
|
6
|
+
const MAX_HEADER_RECORDS = 64;
|
|
7
|
+
export function inspectNativeSessionFile(filePath) {
|
|
8
|
+
if (filePath.endsWith('.opencode-session.json'))
|
|
9
|
+
return inspectOpenCodeSnapshot(filePath);
|
|
10
|
+
const records = readHeaderRecords(filePath);
|
|
11
|
+
if (records.length === 0)
|
|
12
|
+
return { status: 'pending' };
|
|
13
|
+
const stat = fs.statSync(filePath);
|
|
14
|
+
if (filePath.split(/[\\/]+/).includes('.codex')) {
|
|
15
|
+
const meta = records.find((record) => record.type === 'session_meta');
|
|
16
|
+
if (!meta)
|
|
17
|
+
return { status: 'pending' };
|
|
18
|
+
const payload = objectRecord(meta.payload);
|
|
19
|
+
const createdAtMs = readTimestamp(meta.timestamp)
|
|
20
|
+
?? readTimestamp(payload?.timestamp)
|
|
21
|
+
?? validBirthtime(stat.birthtimeMs);
|
|
22
|
+
const sourceSessionKey = typeof payload?.id === 'string' ? payload.id : '';
|
|
23
|
+
return createdAtMs && sourceSessionKey
|
|
24
|
+
? { status: 'ready', createdAtMs, sources: [{ agentType: 'codex', sourceSessionKey }] }
|
|
25
|
+
: { status: 'pending' };
|
|
26
|
+
}
|
|
27
|
+
const firstTimestamp = records.map((record) => readTimestamp(record.timestamp)).find((value) => value != null);
|
|
28
|
+
const createdAtMs = firstTimestamp ?? validBirthtime(stat.birthtimeMs);
|
|
29
|
+
const sourceSessionKey = pathBasenameWithoutJsonl(filePath);
|
|
30
|
+
return createdAtMs && sourceSessionKey
|
|
31
|
+
? { status: 'ready', createdAtMs, sources: [{ agentType: 'claude', sourceSessionKey }] }
|
|
32
|
+
: { status: 'pending' };
|
|
33
|
+
}
|
|
34
|
+
function inspectOpenCodeSnapshot(filePath) {
|
|
35
|
+
let parsed;
|
|
36
|
+
try {
|
|
37
|
+
parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return { status: 'pending' };
|
|
41
|
+
}
|
|
42
|
+
const root = objectRecord(parsed);
|
|
43
|
+
if (!root || !Array.isArray(root.sessions) || root.sessions.length === 0)
|
|
44
|
+
return { status: 'pending' };
|
|
45
|
+
const sourceCreatedAtByKey = {};
|
|
46
|
+
for (const value of root.sessions) {
|
|
47
|
+
const session = objectRecord(value);
|
|
48
|
+
const id = typeof session?.id === 'string' ? session.id : '';
|
|
49
|
+
const createdAtMs = normalizeNumericTimestamp(session?.time_created);
|
|
50
|
+
if (id && createdAtMs)
|
|
51
|
+
sourceCreatedAtByKey[id] = createdAtMs;
|
|
52
|
+
}
|
|
53
|
+
const timestamps = Object.values(sourceCreatedAtByKey);
|
|
54
|
+
if (timestamps.length === 0)
|
|
55
|
+
return { status: 'pending' };
|
|
56
|
+
return {
|
|
57
|
+
status: 'ready',
|
|
58
|
+
createdAtMs: Math.min(...timestamps),
|
|
59
|
+
sourceCreatedAtByKey,
|
|
60
|
+
sources: Object.keys(sourceCreatedAtByKey).map((sourceSessionKey) => ({
|
|
61
|
+
agentType: 'opencode',
|
|
62
|
+
sourceSessionKey,
|
|
63
|
+
})),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function pathBasenameWithoutJsonl(filePath) {
|
|
67
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
68
|
+
const basename = normalized.slice(normalized.lastIndexOf('/') + 1);
|
|
69
|
+
return basename.endsWith('.jsonl') ? basename.slice(0, -'.jsonl'.length) : basename;
|
|
70
|
+
}
|
|
71
|
+
function readHeaderRecords(filePath) {
|
|
72
|
+
const descriptor = fs.openSync(filePath, 'r');
|
|
73
|
+
try {
|
|
74
|
+
const size = Math.min(fs.fstatSync(descriptor).size, MAX_HEADER_BYTES);
|
|
75
|
+
if (size === 0)
|
|
76
|
+
return [];
|
|
77
|
+
const buffer = Buffer.allocUnsafe(size);
|
|
78
|
+
const bytesRead = fs.readSync(descriptor, buffer, 0, size, 0);
|
|
79
|
+
return buffer.subarray(0, bytesRead).toString('utf8').split('\n')
|
|
80
|
+
.slice(0, MAX_HEADER_RECORDS)
|
|
81
|
+
.map((line) => safeParse(line))
|
|
82
|
+
.filter((record) => record != null);
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
fs.closeSync(descriptor);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function objectRecord(value) {
|
|
89
|
+
return typeof value === 'object' && value !== null ? value : null;
|
|
90
|
+
}
|
|
91
|
+
function validBirthtime(value) {
|
|
92
|
+
return Number.isFinite(value) && value > 0 ? value : null;
|
|
93
|
+
}
|
|
94
|
+
export function normalizeNativeTimestamp(value) {
|
|
95
|
+
return normalizeNumericTimestamp(value);
|
|
96
|
+
}
|
|
97
|
+
function normalizeNumericTimestamp(value) {
|
|
98
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
|
|
99
|
+
return null;
|
|
100
|
+
return value < 10_000_000_000 ? Math.round(value * 1000) : Math.round(value);
|
|
101
|
+
}
|
|
@@ -1,2 +1,104 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// @arch docs/architecture/cli/native-session-fusion.md#5-持久同步纪元未知文件分类5-分钟元数据兜底与轮次边界
|
|
2
|
+
// @test src/__tests__/native-fusion-state.test.ts
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { resolvePersonalSyncV1Path } from '../personal-sync/paths.js';
|
|
7
|
+
import { getDaemonInstallationId } from '../session/store.js';
|
|
8
|
+
import { readPersonalDataEpochMarker } from '../personal-sync/epoch.js';
|
|
9
|
+
import { NATIVE_SCANNER_STATE_SCHEMA_VERSION, } from './types.js';
|
|
10
|
+
function stateFile() {
|
|
11
|
+
return resolvePersonalSyncV1Path('native-fusion-state.json');
|
|
12
|
+
}
|
|
13
|
+
export function loadNativeScannerState() {
|
|
14
|
+
const file = stateFile();
|
|
15
|
+
const daemonInstallationId = getDaemonInstallationId();
|
|
16
|
+
if (!fs.existsSync(file)) {
|
|
17
|
+
const epochStartedAt = readPersonalDataEpochMarker()?.epochStartedAt;
|
|
18
|
+
const created = {
|
|
19
|
+
schemaVersion: NATIVE_SCANNER_STATE_SCHEMA_VERSION,
|
|
20
|
+
fusionEpochMs: epochStartedAt ? Date.parse(epochStartedAt) : Date.now(),
|
|
21
|
+
daemonInstallationId,
|
|
22
|
+
files: {},
|
|
23
|
+
};
|
|
24
|
+
atomicWriteJson(file, created);
|
|
25
|
+
return created;
|
|
26
|
+
}
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
30
|
+
}
|
|
31
|
+
catch (cause) {
|
|
32
|
+
throw new Error(`native fusion v1 state is unreadable: ${file}`, { cause });
|
|
33
|
+
}
|
|
34
|
+
validateState(parsed, daemonInstallationId);
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
37
|
+
export function saveNativeScannerState(state) {
|
|
38
|
+
const daemonInstallationId = getDaemonInstallationId();
|
|
39
|
+
validateState(state, daemonInstallationId);
|
|
40
|
+
const file = stateFile();
|
|
41
|
+
if (fs.existsSync(file)) {
|
|
42
|
+
const persisted = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
43
|
+
validateState(persisted, daemonInstallationId);
|
|
44
|
+
if (persisted.fusionEpochMs !== state.fusionEpochMs) {
|
|
45
|
+
throw new Error('native fusion epoch is immutable');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
atomicWriteJson(file, state);
|
|
49
|
+
}
|
|
50
|
+
function validateState(state, daemonInstallationId) {
|
|
51
|
+
if (!state
|
|
52
|
+
|| state.schemaVersion !== NATIVE_SCANNER_STATE_SCHEMA_VERSION
|
|
53
|
+
|| !Number.isSafeInteger(state.fusionEpochMs)
|
|
54
|
+
|| state.fusionEpochMs <= 0
|
|
55
|
+
|| state.daemonInstallationId !== daemonInstallationId
|
|
56
|
+
|| !state.files
|
|
57
|
+
|| typeof state.files !== 'object'
|
|
58
|
+
|| Array.isArray(state.files)) {
|
|
59
|
+
throw new Error('invalid native fusion v1 state');
|
|
60
|
+
}
|
|
61
|
+
for (const cursor of Object.values(state.files)) {
|
|
62
|
+
if (cursor.daemonInstallationId !== daemonInstallationId) {
|
|
63
|
+
throw new Error('native fusion cursor belongs to another daemon installation');
|
|
64
|
+
}
|
|
65
|
+
if (cursor.sources !== undefined && (!Array.isArray(cursor.sources)
|
|
66
|
+
|| cursor.sources.some((source) => (!source
|
|
67
|
+
|| !['codex', 'claude', 'opencode'].includes(source.agentType)
|
|
68
|
+
|| typeof source.sourceSessionKey !== 'string'
|
|
69
|
+
|| !source.sourceSessionKey)))) {
|
|
70
|
+
throw new Error('native fusion cursor has invalid source metadata');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function atomicWriteJson(file, value) {
|
|
75
|
+
const directory = path.dirname(file);
|
|
76
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
77
|
+
const temp = `${file}.tmp-${process.pid}-${crypto.randomBytes(6).toString('hex')}`;
|
|
78
|
+
let descriptor;
|
|
79
|
+
try {
|
|
80
|
+
descriptor = fs.openSync(temp, 'wx', 0o600);
|
|
81
|
+
fs.writeFileSync(descriptor, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
82
|
+
fs.fsyncSync(descriptor);
|
|
83
|
+
fs.closeSync(descriptor);
|
|
84
|
+
descriptor = undefined;
|
|
85
|
+
fs.renameSync(temp, file);
|
|
86
|
+
if (process.platform !== 'win32') {
|
|
87
|
+
const directoryDescriptor = fs.openSync(directory, 'r');
|
|
88
|
+
try {
|
|
89
|
+
fs.fsyncSync(directoryDescriptor);
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
fs.closeSync(directoryDescriptor);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
if (descriptor !== undefined)
|
|
98
|
+
fs.closeSync(descriptor);
|
|
99
|
+
try {
|
|
100
|
+
fs.unlinkSync(temp);
|
|
101
|
+
}
|
|
102
|
+
catch { }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
export const NATIVE_SCANNER_STATE_SCHEMA_VERSION = 1;
|
|
@@ -1 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
// @arch docs/features/machine-owned-personal-session-authority.md#三权威归属模型
|
|
2
|
+
// @test src/__tests__/personal-sync-account-namespace.test.ts
|
|
3
|
+
import { SESSION_SYNC_DATA_EPOCH } from '@shennian/wire';
|
|
4
|
+
import { loadConfig, saveConfig } from '../config/index.js';
|
|
5
|
+
import { getDaemonInstallationId } from './identity.js';
|
|
6
|
+
export async function synchronizeMachinePersonalAuthority(input) {
|
|
7
|
+
const response = await (input.fetchImpl ?? fetch)(`${input.serverUrl.replace(/\/+$/, '')}/api/machines/identity`, {
|
|
8
|
+
headers: { Authorization: `Bearer ${input.machineToken}` },
|
|
9
|
+
signal: AbortSignal.timeout(10_000),
|
|
10
|
+
});
|
|
11
|
+
if (!response.ok)
|
|
12
|
+
throw new Error(`machine_personal_authority_unavailable:${response.status}`);
|
|
13
|
+
const body = await response.json();
|
|
14
|
+
if (typeof body.machineId !== 'string' || !body.machineId
|
|
15
|
+
|| typeof body.daemonInstallationId !== 'string' || !body.daemonInstallationId
|
|
16
|
+
|| typeof body.dataEpoch !== 'number' || !Number.isSafeInteger(body.dataEpoch))
|
|
17
|
+
throw new Error('machine_personal_authority_invalid_response');
|
|
18
|
+
if (input.machineId && body.machineId !== input.machineId) {
|
|
19
|
+
throw new Error('machine_personal_authority_machine_mismatch');
|
|
20
|
+
}
|
|
21
|
+
if (body.daemonInstallationId !== getDaemonInstallationId()) {
|
|
22
|
+
throw new Error('machine_personal_authority_daemon_mismatch');
|
|
23
|
+
}
|
|
24
|
+
if (body.dataEpoch !== SESSION_SYNC_DATA_EPOCH) {
|
|
25
|
+
throw new Error('machine_personal_authority_epoch_mismatch');
|
|
26
|
+
}
|
|
27
|
+
const config = loadConfig();
|
|
28
|
+
if (config.machineId !== body.machineId) {
|
|
29
|
+
// accountNamespaceId is deliberately left untouched as legacy pairing
|
|
30
|
+
// metadata. It never selects, clears, or authorizes personal storage.
|
|
31
|
+
saveConfig({ ...config, machineId: body.machineId });
|
|
32
|
+
}
|
|
33
|
+
return body;
|
|
34
|
+
}
|
|
@@ -1,2 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// @arch docs/features/network-rooms/core-data-and-security-decisions.md#四默认自动接收不使用-manual-作为正常模式
|
|
2
|
+
// @test src/__tests__/room-activation-audit.test.ts
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { resolveShennianPath } from '../config/index.js';
|
|
6
|
+
export class RoomActivationAudit {
|
|
7
|
+
root;
|
|
8
|
+
constructor(root = resolveShennianPath('rooms')) {
|
|
9
|
+
this.root = root;
|
|
10
|
+
}
|
|
11
|
+
record(event) {
|
|
12
|
+
for (const value of [event.roomId, event.bindingId, event.causeMessageId, event.rootCauseMessageId]) {
|
|
13
|
+
if (!/^[A-Za-z0-9_.:@-]{1,191}$/.test(value))
|
|
14
|
+
throw new Error('invalid_room_activation_audit_id');
|
|
15
|
+
}
|
|
16
|
+
if (!Number.isInteger(event.hopCount) || event.hopCount < 0 || event.hopCount > 32) {
|
|
17
|
+
throw new Error('invalid_room_activation_audit_hop');
|
|
18
|
+
}
|
|
19
|
+
const directory = path.join(this.root, event.roomId);
|
|
20
|
+
fs.mkdirSync(this.root, { recursive: true, mode: 0o700 });
|
|
21
|
+
if (fs.existsSync(directory)) {
|
|
22
|
+
const stat = fs.lstatSync(directory);
|
|
23
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
24
|
+
throw new Error('unsafe_room_activation_audit_path');
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
fs.mkdirSync(directory, { mode: 0o700 });
|
|
28
|
+
}
|
|
29
|
+
if (process.platform !== 'win32')
|
|
30
|
+
fs.chmodSync(directory, 0o700);
|
|
31
|
+
const file = path.join(directory, 'activation-audit.jsonl');
|
|
32
|
+
if (fs.existsSync(file)) {
|
|
33
|
+
const stat = fs.lstatSync(file);
|
|
34
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
35
|
+
throw new Error('unsafe_room_activation_audit_path');
|
|
36
|
+
}
|
|
37
|
+
fs.appendFileSync(file, `${JSON.stringify({
|
|
38
|
+
schemaVersion: 1,
|
|
39
|
+
...event,
|
|
40
|
+
at: (event.at ?? new Date()).toISOString(),
|
|
41
|
+
})}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
42
|
+
if (process.platform !== 'win32')
|
|
43
|
+
fs.chmodSync(file, 0o600);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
// @arch docs/features/network-rooms/core-data-and-security-decisions.md#五触发语义
|
|
2
|
+
// @test src/__tests__/room-activation-policy.test.ts
|
|
3
|
+
export function selectRoomActivationTargets(message, registrations) {
|
|
4
|
+
if (message.deletedAt || message.kind === 'system')
|
|
5
|
+
return [];
|
|
6
|
+
const mentionedAgents = new Set((message.mentions ?? [])
|
|
7
|
+
.filter((mention) => mention.principalType === 'agent')
|
|
8
|
+
.map((mention) => mention.membershipId));
|
|
9
|
+
const eligible = registrations.filter(isEligible);
|
|
10
|
+
if (message.author.principalType === 'agent') {
|
|
11
|
+
return eligible
|
|
12
|
+
.filter((registration) => registration.binding.agentMembershipId !== message.author.membershipId &&
|
|
13
|
+
mentionedAgents.has(registration.binding.agentMembershipId))
|
|
14
|
+
.map((registration) => ({ registration, cause: 'explicit_mention' }));
|
|
15
|
+
}
|
|
16
|
+
if (mentionedAgents.size > 0) {
|
|
17
|
+
return eligible
|
|
18
|
+
.filter((registration) => mentionedAgents.has(registration.binding.agentMembershipId))
|
|
19
|
+
.map((registration) => ({ registration, cause: 'explicit_mention' }));
|
|
20
|
+
}
|
|
21
|
+
return eligible
|
|
22
|
+
.filter((registration) => registration.binding.activationMode === 'auto_follow')
|
|
23
|
+
.map((registration) => ({ registration, cause: 'human_message' }));
|
|
24
|
+
}
|
|
25
|
+
function isEligible(registration) {
|
|
26
|
+
return Boolean(registration.lifecycleState !== 'closed' &&
|
|
27
|
+
registration.binding?.status === 'active' &&
|
|
28
|
+
registration.binding.activationMode !== 'paused' &&
|
|
29
|
+
registration.binding.agentMembershipId);
|
|
30
|
+
}
|
|
@@ -21,6 +21,7 @@ export type RoomActivationContext = {
|
|
|
21
21
|
displayName: string;
|
|
22
22
|
description: string;
|
|
23
23
|
sessionMode: SessionMode;
|
|
24
|
+
managerConfig?: import('@shennian/wire').ManagerConfig | null;
|
|
24
25
|
permissionPolicy: ManagedAgentPermissionPolicy;
|
|
25
26
|
permissionConfirmation: ManagedAgentPermissionConfirmation | null;
|
|
26
27
|
}>;
|