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.
Files changed (98) hide show
  1. package/README.md +3 -2
  2. package/dist/bin/shennian.js +1 -1
  3. package/dist/publish-build-manifest.json +32 -27
  4. package/dist/src/agents/adapter.js +1 -19
  5. package/dist/src/agents/claude-managed-permissions.js +1 -248
  6. package/dist/src/agents/claude.js +8 -393
  7. package/dist/src/agents/codex-managed-permissions.js +1 -294
  8. package/dist/src/agents/codex-managed-room-tools.js +1 -249
  9. package/dist/src/agents/codex-utils.js +7 -200
  10. package/dist/src/agents/codex.d.ts +7 -1
  11. package/dist/src/agents/codex.js +17 -1352
  12. package/dist/src/agents/config-status.js +1 -226
  13. package/dist/src/agents/cursor-managed-permissions.js +2 -299
  14. package/dist/src/agents/cursor.js +1 -314
  15. package/dist/src/agents/custom.js +4 -418
  16. package/dist/src/agents/external-channel-instructions.js +11 -94
  17. package/dist/src/agents/gemini.js +1 -180
  18. package/dist/src/agents/manager.js +18 -250
  19. package/dist/src/agents/model-registry/cache.js +1 -37
  20. package/dist/src/agents/model-registry/discovery.js +2 -188
  21. package/dist/src/agents/model-registry/parsers.js +4 -521
  22. package/dist/src/agents/model-registry/runner.js +1 -30
  23. package/dist/src/agents/model-registry/service.js +1 -115
  24. package/dist/src/agents/model-registry/types.js +1 -8
  25. package/dist/src/agents/model-registry.js +1 -19
  26. package/dist/src/agents/native-capabilities.js +1 -237
  27. package/dist/src/agents/openclaw.js +2 -293
  28. package/dist/src/agents/opencode-managed-permissions.js +2 -187
  29. package/dist/src/agents/opencode.js +1 -298
  30. package/dist/src/agents/platform-instructions.js +17 -46
  31. package/dist/src/agents/runtime-compatibility.js +1 -44
  32. package/dist/src/agents/workbuddy-managed-permissions.js +2 -329
  33. package/dist/src/agents/workbuddy.js +3 -444
  34. package/dist/src/commands/logout.d.ts +3 -0
  35. package/dist/src/commands/logout.js +1 -0
  36. package/dist/src/daemon-ipc/paths.js +1 -1
  37. package/dist/src/fs/boundary.js +1 -185
  38. package/dist/src/fs/handler.js +1 -130
  39. package/dist/src/fs/security.js +1 -32
  40. package/dist/src/fs/text-decoder.js +1 -110
  41. package/dist/src/index.js +3 -587
  42. package/dist/src/manager/prompt.js +29 -34
  43. package/dist/src/manager/registry.js +1 -317
  44. package/dist/src/manager/retired-external-channel-compat.js +1 -40
  45. package/dist/src/manager/runtime.js +23 -1514
  46. package/dist/src/native-fusion/codex-parser.d.ts +3 -1
  47. package/dist/src/native-fusion/codex-parser.js +4 -660
  48. package/dist/src/native-fusion/codex-user-input.js +5 -102
  49. package/dist/src/native-fusion/config.js +1 -5
  50. package/dist/src/native-fusion/file-watcher.js +1 -191
  51. package/dist/src/native-fusion/local-repository.js +2 -569
  52. package/dist/src/native-fusion/opencode-parser.js +3 -130
  53. package/dist/src/native-fusion/parser-common.d.ts +6 -1
  54. package/dist/src/native-fusion/parser-common.js +4 -4
  55. package/dist/src/native-fusion/parsers.d.ts +4 -2
  56. package/dist/src/native-fusion/parsers.js +3 -140
  57. package/dist/src/native-fusion/service.js +2 -739
  58. package/dist/src/native-fusion/session-header.js +2 -101
  59. package/dist/src/native-fusion/state.js +2 -104
  60. package/dist/src/native-fusion/types.js +1 -1
  61. package/dist/src/personal-sync/machine-authority.js +1 -34
  62. package/dist/src/room/activation-audit.js +2 -45
  63. package/dist/src/room/activation-policy.js +1 -30
  64. package/dist/src/room/activation-service.js +8 -408
  65. package/dist/src/room/invite-bootstrap.js +1 -156
  66. package/dist/src/room/managed-prompt-composer.d.ts +1 -1
  67. package/dist/src/room/managed-prompt-composer.js +17 -16
  68. package/dist/src/room/managed-room-binding.js +1 -167
  69. package/dist/src/room/managed-room-ipc.d.ts +4 -0
  70. package/dist/src/room/managed-room-ipc.js +2 -2
  71. package/dist/src/room/mirror-sync-service.d.ts +1 -0
  72. package/dist/src/room/mirror-sync-service.js +1 -183
  73. package/dist/src/room/room-binding-client.js +1 -151
  74. package/dist/src/session/archive-zip.js +1 -220
  75. package/dist/src/session/deletion-sync-state.js +2 -136
  76. package/dist/src/session/handlers/agent-capabilities.js +1 -14
  77. package/dist/src/session/handlers/agent-config.js +1 -150
  78. package/dist/src/session/handlers/agents.js +1 -55
  79. package/dist/src/session/handlers/chat.js +2 -1030
  80. package/dist/src/session/handlers/control.js +1 -61
  81. package/dist/src/session/handlers/fs.js +1 -594
  82. package/dist/src/session/handlers/message-status.js +1 -15
  83. package/dist/src/session/handlers/remote-access.js +1 -43
  84. package/dist/src/session/handlers/room-managed.js +1 -101
  85. package/dist/src/session/handlers/session-refresh.js +1 -35
  86. package/dist/src/session/handlers/title.js +1 -79
  87. package/dist/src/session/handlers/tool-detail.js +1 -149
  88. package/dist/src/session/handlers/voice-window.js +1 -62
  89. package/dist/src/session/history-subscriptions.js +1 -470
  90. package/dist/src/session/manager.d.ts +1 -0
  91. package/dist/src/session/manager.js +1 -658
  92. package/dist/src/session/native-cleanup-outbox.js +2 -287
  93. package/dist/src/session/native-session-lifecycle.js +6 -327
  94. package/dist/src/session/queue.d.ts +8 -0
  95. package/dist/src/session/queue.js +6 -6
  96. package/dist/src/upgrade/scheduler.js +1 -126
  97. package/node_modules/@shennian/wire/dist/agent-workspace.js +2 -2
  98. package/package.json +1 -1
@@ -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
+ import a from"node:fs";import{readTimestamp as d,safeParse as g}from"./parser-common.js";const S=1024*1024,b=64;function j(t){if(t.endsWith(".opencode-session.json"))return h(t);const s=A(t);if(s.length===0)return{status:"pending"};const e=a.statSync(t);if(t.split(/[\\/]+/).includes(".codex")){const n=s.find(y=>y.type==="session_meta");if(!n)return{status:"pending"};const c=p(n.payload),u=d(n.timestamp)??d(c?.timestamp)??f(e.birthtimeMs),m=typeof c?.id=="string"?c.id:"";return u&&m?{status:"ready",createdAtMs:u,sources:[{agentType:"codex",sourceSessionKey:m}]}:{status:"pending"}}const o=s.map(n=>d(n.timestamp)).find(n=>n!=null)??f(e.birthtimeMs),r=M(t);return o&&r?{status:"ready",createdAtMs:o,sources:[{agentType:"claude",sourceSessionKey:r}]}:{status:"pending"}}function h(t){let s;try{s=JSON.parse(a.readFileSync(t,"utf8"))}catch{return{status:"pending"}}const e=p(s);if(!e||!Array.isArray(e.sessions)||e.sessions.length===0)return{status:"pending"};const i={};for(const r of e.sessions){const n=p(r),c=typeof n?.id=="string"?n.id:"",u=l(n?.time_created);c&&u&&(i[c]=u)}const o=Object.values(i);return o.length===0?{status:"pending"}:{status:"ready",createdAtMs:Math.min(...o),sourceCreatedAtByKey:i,sources:Object.keys(i).map(r=>({agentType:"opencode",sourceSessionKey:r}))}}function M(t){const s=t.replace(/\\/g,"/"),e=s.slice(s.lastIndexOf("/")+1);return e.endsWith(".jsonl")?e.slice(0,-6):e}function A(t){const s=a.openSync(t,"r");try{const e=Math.min(a.fstatSync(s).size,S);if(e===0)return[];const i=Buffer.allocUnsafe(e),o=a.readSync(s,i,0,e,0);return i.subarray(0,o).toString("utf8").split(`
2
+ `).slice(0,b).map(r=>g(r)).filter(r=>r!=null)}finally{a.closeSync(s)}}function p(t){return typeof t=="object"&&t!==null?t:null}function f(t){return Number.isFinite(t)&&t>0?t:null}function R(t){return l(t)}function l(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?null:t<1e10?Math.round(t*1e3):Math.round(t)}export{j as inspectNativeSessionFile,R as normalizeNativeTimestamp};
@@ -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
+ import d from"node:crypto";import r from"node:fs";import p from"node:path";import{resolvePersonalSyncV1Path as y}from"../personal-sync/paths.js";import{getDaemonInstallationId as a}from"../session/store.js";import{readPersonalDataEpochMarker as m}from"../personal-sync/epoch.js";import{NATIVE_SCANNER_STATE_SCHEMA_VERSION as f}from"./types.js";function l(){return y("native-fusion-state.json")}function N(){const e=l(),t=a();if(!r.existsSync(e)){const n=m()?.epochStartedAt,i={schemaVersion:f,fusionEpochMs:n?Date.parse(n):Date.now(),daemonInstallationId:t,files:{}};return u(e,i),i}let o;try{o=JSON.parse(r.readFileSync(e,"utf8"))}catch(n){throw new Error(`native fusion v1 state is unreadable: ${e}`,{cause:n})}return s(o,t),o}function g(e){const t=a();s(e,t);const o=l();if(r.existsSync(o)){const n=JSON.parse(r.readFileSync(o,"utf8"));if(s(n,t),n.fusionEpochMs!==e.fusionEpochMs)throw new Error("native fusion epoch is immutable")}u(o,e)}function s(e,t){if(!e||e.schemaVersion!==f||!Number.isSafeInteger(e.fusionEpochMs)||e.fusionEpochMs<=0||e.daemonInstallationId!==t||!e.files||typeof e.files!="object"||Array.isArray(e.files))throw new Error("invalid native fusion v1 state");for(const o of Object.values(e.files)){if(o.daemonInstallationId!==t)throw new Error("native fusion cursor belongs to another daemon installation");if(o.sources!==void 0&&(!Array.isArray(o.sources)||o.sources.some(n=>!n||!["codex","claude","opencode"].includes(n.agentType)||typeof n.sourceSessionKey!="string"||!n.sourceSessionKey)))throw new Error("native fusion cursor has invalid source metadata")}}function u(e,t){const o=p.dirname(e);r.mkdirSync(o,{recursive:!0,mode:448});const n=`${e}.tmp-${process.pid}-${d.randomBytes(6).toString("hex")}`;let i;try{if(i=r.openSync(n,"wx",384),r.writeFileSync(i,`${JSON.stringify(t,null,2)}
2
+ `,"utf8"),r.fsyncSync(i),r.closeSync(i),i=void 0,r.renameSync(n,e),process.platform!=="win32"){const c=r.openSync(o,"r");try{r.fsyncSync(c)}finally{r.closeSync(c)}}}finally{i!==void 0&&r.closeSync(i);try{r.unlinkSync(n)}catch{}}}export{N as loadNativeScannerState,g as saveNativeScannerState};
@@ -1 +1 @@
1
- export const NATIVE_SCANNER_STATE_SCHEMA_VERSION = 1;
1
+ const E=1;export{E as NATIVE_SCANNER_STATE_SCHEMA_VERSION};
@@ -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
+ import{SESSION_SYNC_DATA_EPOCH as r}from"@shennian/wire";import{loadConfig as i,saveConfig as t}from"../config/index.js";import{getDaemonInstallationId as h}from"./identity.js";async function d(e){const n=await(e.fetchImpl??fetch)(`${e.serverUrl.replace(/\/+$/,"")}/api/machines/identity`,{headers:{Authorization:`Bearer ${e.machineToken}`},signal:AbortSignal.timeout(1e4)});if(!n.ok)throw new Error(`machine_personal_authority_unavailable:${n.status}`);const a=await n.json();if(typeof a.machineId!="string"||!a.machineId||typeof a.daemonInstallationId!="string"||!a.daemonInstallationId||typeof a.dataEpoch!="number"||!Number.isSafeInteger(a.dataEpoch))throw new Error("machine_personal_authority_invalid_response");if(e.machineId&&a.machineId!==e.machineId)throw new Error("machine_personal_authority_machine_mismatch");if(a.daemonInstallationId!==h())throw new Error("machine_personal_authority_daemon_mismatch");if(a.dataEpoch!==r)throw new Error("machine_personal_authority_epoch_mismatch");const o=i();return o.machineId!==a.machineId&&t({...o,machineId:a.machineId}),a}export{d as synchronizeMachinePersonalAuthority};
@@ -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
+ import i from"node:fs";import n from"node:path";import{resolveShennianPath as a}from"../config/index.js";class f{root;constructor(o=a("rooms")){this.root=o}record(o){for(const t of[o.roomId,o.bindingId,o.causeMessageId,o.rootCauseMessageId])if(!/^[A-Za-z0-9_.:@-]{1,191}$/.test(t))throw new Error("invalid_room_activation_audit_id");if(!Number.isInteger(o.hopCount)||o.hopCount<0||o.hopCount>32)throw new Error("invalid_room_activation_audit_hop");const r=n.join(this.root,o.roomId);if(i.mkdirSync(this.root,{recursive:!0,mode:448}),i.existsSync(r)){const t=i.lstatSync(r);if(!t.isDirectory()||t.isSymbolicLink())throw new Error("unsafe_room_activation_audit_path")}else i.mkdirSync(r,{mode:448});process.platform!=="win32"&&i.chmodSync(r,448);const s=n.join(r,"activation-audit.jsonl");if(i.existsSync(s)){const t=i.lstatSync(s);if(!t.isFile()||t.isSymbolicLink())throw new Error("unsafe_room_activation_audit_path")}i.appendFileSync(s,`${JSON.stringify({schemaVersion:1,...o,at:(o.at??new Date).toISOString()})}
2
+ `,{encoding:"utf8",mode:384}),process.platform!=="win32"&&i.chmodSync(s,384)}}export{f as RoomActivationAudit};
@@ -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
- }
1
+ function l(i,a){if(i.deletedAt||i.kind==="system")return[];const n=new Set((i.mentions??[]).filter(e=>e.principalType==="agent").map(e=>e.membershipId)),t=a.filter(d);return i.author.principalType==="agent"?t.filter(e=>e.binding.agentMembershipId!==i.author.membershipId&&n.has(e.binding.agentMembershipId)).map(e=>({registration:e,cause:"explicit_mention"})):n.size>0?t.filter(e=>n.has(e.binding.agentMembershipId)).map(e=>({registration:e,cause:"explicit_mention"})):t.filter(e=>e.binding.activationMode==="auto_follow").map(e=>({registration:e,cause:"human_message"}))}function d(i){return!!(i.lifecycleState!=="closed"&&i.binding?.status==="active"&&i.binding.activationMode!=="paused"&&i.binding.agentMembershipId)}export{l as selectRoomActivationTargets};