shennian 0.3.85 → 0.3.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/shennian.js +1 -1
- package/dist/publish-build-manifest.json +28 -28
- package/dist/src/agents/adapter.js +19 -1
- package/dist/src/agents/claude-managed-permissions.js +248 -1
- package/dist/src/agents/claude.js +393 -8
- package/dist/src/agents/codex-managed-permissions.js +294 -1
- package/dist/src/agents/codex-managed-room-tools.js +249 -1
- package/dist/src/agents/codex-utils.js +200 -7
- package/dist/src/agents/codex.js +1352 -17
- package/dist/src/agents/config-status.js +226 -1
- package/dist/src/agents/cursor-managed-permissions.js +299 -2
- package/dist/src/agents/cursor.js +314 -1
- package/dist/src/agents/custom.js +418 -4
- package/dist/src/agents/external-channel-instructions.js +94 -11
- package/dist/src/agents/gemini.js +180 -1
- package/dist/src/agents/manager.d.ts +2 -0
- package/dist/src/agents/manager.js +250 -18
- package/dist/src/agents/model-registry/cache.js +37 -1
- package/dist/src/agents/model-registry/discovery.js +188 -2
- package/dist/src/agents/model-registry/parsers.js +521 -4
- package/dist/src/agents/model-registry/runner.js +30 -1
- package/dist/src/agents/model-registry/service.js +115 -1
- package/dist/src/agents/model-registry/types.js +8 -1
- package/dist/src/agents/model-registry.js +19 -1
- package/dist/src/agents/native-capabilities.js +237 -1
- package/dist/src/agents/openclaw.js +293 -2
- package/dist/src/agents/opencode-managed-permissions.js +187 -2
- package/dist/src/agents/opencode.js +298 -1
- package/dist/src/agents/platform-instructions.js +46 -17
- package/dist/src/agents/runtime-compatibility.js +44 -1
- package/dist/src/agents/workbuddy-managed-permissions.js +329 -2
- package/dist/src/agents/workbuddy.js +444 -3
- package/dist/src/commands/room.js +2 -2
- package/dist/src/fs/boundary.js +185 -1
- package/dist/src/fs/handler.js +130 -1
- package/dist/src/fs/security.js +32 -1
- package/dist/src/fs/text-decoder.js +110 -1
- package/dist/src/index.js +587 -3
- package/dist/src/manager/prompt.js +34 -29
- package/dist/src/manager/registry.js +317 -1
- package/dist/src/manager/retired-external-channel-compat.js +40 -1
- package/dist/src/manager/runtime.d.ts +4 -0
- package/dist/src/manager/runtime.js +1514 -23
- package/dist/src/native-fusion/codex-parser.js +660 -4
- package/dist/src/native-fusion/codex-user-input.js +102 -5
- package/dist/src/native-fusion/config.js +5 -1
- package/dist/src/native-fusion/file-watcher.js +191 -1
- package/dist/src/native-fusion/local-repository.js +569 -2
- package/dist/src/native-fusion/opencode-parser.js +130 -3
- package/dist/src/native-fusion/parsers.js +140 -3
- package/dist/src/native-fusion/service.js +739 -2
- package/dist/src/native-fusion/session-header.js +101 -2
- package/dist/src/native-fusion/state.js +104 -2
- package/dist/src/native-fusion/types.js +1 -1
- package/dist/src/personal-sync/machine-authority.js +34 -1
- package/dist/src/room/activation-audit.js +45 -2
- package/dist/src/room/activation-policy.js +30 -1
- package/dist/src/room/activation-service.d.ts +1 -0
- package/dist/src/room/activation-service.js +408 -8
- package/dist/src/room/invite-bootstrap.js +156 -1
- package/dist/src/room/managed-context.d.ts +1 -1
- package/dist/src/room/managed-context.js +1 -1
- package/dist/src/room/managed-prompt-composer.d.ts +1 -1
- package/dist/src/room/managed-prompt-composer.js +6 -5
- package/dist/src/room/managed-room-binding.js +167 -1
- package/dist/src/room/managed-room-ipc.d.ts +2 -2
- package/dist/src/room/managed-room-ipc.js +2 -2
- package/dist/src/room/mirror-sync-service.js +183 -1
- package/dist/src/room/room-action-client.d.ts +11 -0
- package/dist/src/room/room-action-client.js +1 -1
- package/dist/src/room/room-binding-client.d.ts +1 -0
- package/dist/src/room/room-binding-client.js +151 -1
- package/dist/src/security/managed-agent-policy.d.ts +3 -2
- package/dist/src/security/managed-agent-policy.js +1 -1
- package/dist/src/session/archive-zip.js +220 -1
- package/dist/src/session/deletion-sync-state.js +136 -2
- package/dist/src/session/handlers/agent-capabilities.js +14 -1
- package/dist/src/session/handlers/agent-config.js +150 -1
- package/dist/src/session/handlers/agents.js +55 -1
- package/dist/src/session/handlers/chat.d.ts +1 -1
- package/dist/src/session/handlers/chat.js +1030 -2
- package/dist/src/session/handlers/control.js +61 -1
- package/dist/src/session/handlers/fs.js +594 -1
- package/dist/src/session/handlers/message-status.js +15 -1
- package/dist/src/session/handlers/remote-access.js +43 -1
- package/dist/src/session/handlers/room-managed.js +101 -1
- package/dist/src/session/handlers/session-refresh.js +35 -1
- package/dist/src/session/handlers/title.js +79 -1
- package/dist/src/session/handlers/tool-detail.js +149 -1
- package/dist/src/session/handlers/voice-window.js +62 -1
- package/dist/src/session/history-subscriptions.js +470 -1
- package/dist/src/session/manager.js +658 -1
- package/dist/src/session/native-cleanup-outbox.js +287 -2
- package/dist/src/session/native-session-lifecycle.js +327 -6
- package/dist/src/session/queue.d.ts +5 -0
- package/dist/src/session/queue.js +8 -8
- package/dist/src/upgrade/scheduler.js +126 -1
- package/node_modules/@shennian/wire/dist/agent-workspace.js +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,102 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// @arch docs/architecture/cli/native-session-fusion.md
|
|
2
|
+
// @test src/__tests__/native-fusion-codex-delegation.test.ts
|
|
3
|
+
// @test src/__tests__/native-fusion-codex-current-input.test.ts
|
|
4
|
+
import { normalizeText } from './parser-common.js';
|
|
5
|
+
export function parseCodexTextElements(value) {
|
|
6
|
+
if (!Array.isArray(value))
|
|
7
|
+
return '';
|
|
8
|
+
return value
|
|
9
|
+
.map((item) => {
|
|
10
|
+
if (typeof item === 'string')
|
|
11
|
+
return item;
|
|
12
|
+
if (typeof item !== 'object' || item === null)
|
|
13
|
+
return '';
|
|
14
|
+
const record = item;
|
|
15
|
+
return typeof record.text === 'string' ? record.text : '';
|
|
16
|
+
})
|
|
17
|
+
.map((text) => normalizeText(text))
|
|
18
|
+
.filter(Boolean)
|
|
19
|
+
.join('\n\n');
|
|
20
|
+
}
|
|
21
|
+
function stripCodexImagePlaceholders(text) {
|
|
22
|
+
return normalizeText(text.replace(/<image>\s*<\/image>/gi, ''));
|
|
23
|
+
}
|
|
24
|
+
const CODEX_AMBIENT_BROWSER_REQUEST_RE = /^<in-app-browser-context\b(?=[^>]*\bsource=["']ambient-ui-state["'])[^>]*>[\s\S]*?<\/in-app-browser-context>\s*\n+##\s+My request(?: for Codex)?:\s*([\s\S]*)$/i;
|
|
25
|
+
export function stripCodexUserMessageWrapper(text) {
|
|
26
|
+
const normalized = normalizeText(text);
|
|
27
|
+
if (!normalized)
|
|
28
|
+
return { text: '', strippedWrapper: false };
|
|
29
|
+
const ambientBrowserRequestMatch = normalized.match(CODEX_AMBIENT_BROWSER_REQUEST_RE);
|
|
30
|
+
if (ambientBrowserRequestMatch?.[1]) {
|
|
31
|
+
return {
|
|
32
|
+
text: stripCodexImagePlaceholders(ambientBrowserRequestMatch[1]),
|
|
33
|
+
strippedWrapper: true,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const requestMatch = normalized.match(/My request for Codex:\s*([\s\S]*)$/i);
|
|
37
|
+
if (requestMatch?.[1]) {
|
|
38
|
+
return { text: stripCodexImagePlaceholders(requestMatch[1]), strippedWrapper: true };
|
|
39
|
+
}
|
|
40
|
+
const attachmentRequestMatch = normalized.match(/^# Files mentioned by the user:\s*[\s\S]*?\n+## My request:\s*([\s\S]*)$/i);
|
|
41
|
+
if (attachmentRequestMatch?.[1]) {
|
|
42
|
+
return { text: stripCodexImagePlaceholders(attachmentRequestMatch[1]), strippedWrapper: true };
|
|
43
|
+
}
|
|
44
|
+
return { text: normalized, strippedWrapper: false };
|
|
45
|
+
}
|
|
46
|
+
const CODEX_IMAGE_OPEN_TAG_RE = /^<image\b[^>]*\bpath="([^"]+)"[^>]*>\s*$/i;
|
|
47
|
+
const CODEX_IMAGE_CLOSE_TAG_RE = /^<\/image>\s*$/i;
|
|
48
|
+
export function parseCodexResponseUserInput(content) {
|
|
49
|
+
if (!Array.isArray(content))
|
|
50
|
+
return { text: '', imagePaths: [], strippedWrapper: false };
|
|
51
|
+
const ignoredTextIndexes = new Set();
|
|
52
|
+
const imagePaths = [];
|
|
53
|
+
for (let index = 0; index + 2 < content.length; index += 1) {
|
|
54
|
+
const opening = content[index];
|
|
55
|
+
const image = content[index + 1];
|
|
56
|
+
const closing = content[index + 2];
|
|
57
|
+
if (typeof opening !== 'object' || opening === null ||
|
|
58
|
+
typeof image !== 'object' || image === null ||
|
|
59
|
+
typeof closing !== 'object' || closing === null)
|
|
60
|
+
continue;
|
|
61
|
+
const openingRecord = opening;
|
|
62
|
+
const imageRecord = image;
|
|
63
|
+
const closingRecord = closing;
|
|
64
|
+
if (openingRecord.type !== 'input_text' || typeof openingRecord.text !== 'string' ||
|
|
65
|
+
imageRecord.type !== 'input_image' ||
|
|
66
|
+
closingRecord.type !== 'input_text' || typeof closingRecord.text !== 'string' ||
|
|
67
|
+
!CODEX_IMAGE_CLOSE_TAG_RE.test(normalizeText(closingRecord.text)))
|
|
68
|
+
continue;
|
|
69
|
+
const imagePath = normalizeText(openingRecord.text).match(CODEX_IMAGE_OPEN_TAG_RE)?.[1];
|
|
70
|
+
if (!imagePath)
|
|
71
|
+
continue;
|
|
72
|
+
imagePaths.push(imagePath);
|
|
73
|
+
ignoredTextIndexes.add(index);
|
|
74
|
+
ignoredTextIndexes.add(index + 2);
|
|
75
|
+
}
|
|
76
|
+
const rawText = content
|
|
77
|
+
.map((part, index) => {
|
|
78
|
+
if (ignoredTextIndexes.has(index))
|
|
79
|
+
return '';
|
|
80
|
+
if (typeof part === 'string')
|
|
81
|
+
return part;
|
|
82
|
+
if (typeof part !== 'object' || part === null)
|
|
83
|
+
return '';
|
|
84
|
+
const record = part;
|
|
85
|
+
return record.type === 'input_text' && typeof record.text === 'string' ? record.text : '';
|
|
86
|
+
})
|
|
87
|
+
.map((text) => normalizeText(text))
|
|
88
|
+
.filter(Boolean)
|
|
89
|
+
.join('\n\n');
|
|
90
|
+
const stripped = stripCodexUserMessageWrapper(rawText);
|
|
91
|
+
return { ...stripped, imagePaths };
|
|
92
|
+
}
|
|
93
|
+
export function parseCodexDelegationInput(payload) {
|
|
94
|
+
if (payload.namespace !== 'codex_app' || typeof payload.output !== 'string')
|
|
95
|
+
return null;
|
|
96
|
+
const normalized = normalizeText(payload.output);
|
|
97
|
+
if (!normalized.startsWith('<codex_delegation>') || !normalized.endsWith('</codex_delegation>')) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const input = normalized.match(/<input>([\s\S]*?)<\/input>\s*<\/codex_delegation>$/)?.[1];
|
|
101
|
+
return input ? normalizeText(input) : null;
|
|
102
|
+
}
|
|
@@ -1 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
// @arch docs/architecture/cli/native-session-fusion.md
|
|
2
|
+
// @test src/__tests__/native-fusion-config.test.ts
|
|
3
|
+
export function isNativeFusionEnabled(env = process.env) {
|
|
4
|
+
return env.SHENNIAN_NATIVE_FUSION_DISABLED !== '1';
|
|
5
|
+
}
|
|
@@ -1 +1,191 @@
|
|
|
1
|
-
|
|
1
|
+
// @arch docs/architecture/cli/native-session-fusion.md#51-watcher-拓扑与事件规则
|
|
2
|
+
// @test src/__tests__/native-fusion-file-watcher.test.ts
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
const WATCH_DEBOUNCE_MS = 250;
|
|
6
|
+
export class NativeFusionFileWatcher {
|
|
7
|
+
onChanged;
|
|
8
|
+
directoryWatchFactory;
|
|
9
|
+
fileWatchFactory;
|
|
10
|
+
directoryWatchers = new Map();
|
|
11
|
+
fileWatchers = new Map();
|
|
12
|
+
filesByDirectory = new Map();
|
|
13
|
+
debounceTimers = new Map();
|
|
14
|
+
constructor(onChanged, directoryWatchFactory = (directory, listener) => (fs.watch(directory, listener)), fileWatchFactory = (filePath, listener) => fs.watch(filePath, listener)) {
|
|
15
|
+
this.onChanged = onChanged;
|
|
16
|
+
this.directoryWatchFactory = directoryWatchFactory;
|
|
17
|
+
this.fileWatchFactory = fileWatchFactory;
|
|
18
|
+
}
|
|
19
|
+
refresh(filePaths, rootDirectories = []) {
|
|
20
|
+
const nextFiles = new Set(filePaths);
|
|
21
|
+
const nextFilesByDirectory = new Map();
|
|
22
|
+
for (const filePath of filePaths) {
|
|
23
|
+
const directory = path.dirname(filePath);
|
|
24
|
+
const files = nextFilesByDirectory.get(directory) ?? new Set();
|
|
25
|
+
files.add(filePath);
|
|
26
|
+
nextFilesByDirectory.set(directory, files);
|
|
27
|
+
}
|
|
28
|
+
const nextDirectories = new Set(nextFilesByDirectory.keys());
|
|
29
|
+
for (const root of rootDirectories) {
|
|
30
|
+
this.collectDirectories(root, nextDirectories);
|
|
31
|
+
}
|
|
32
|
+
for (const [directory, watcher] of this.directoryWatchers.entries()) {
|
|
33
|
+
if (nextDirectories.has(directory))
|
|
34
|
+
continue;
|
|
35
|
+
watcher.close();
|
|
36
|
+
this.directoryWatchers.delete(directory);
|
|
37
|
+
}
|
|
38
|
+
for (const [filePath, watcher] of this.fileWatchers.entries()) {
|
|
39
|
+
if (nextFiles.has(filePath))
|
|
40
|
+
continue;
|
|
41
|
+
watcher.close();
|
|
42
|
+
this.fileWatchers.delete(filePath);
|
|
43
|
+
}
|
|
44
|
+
this.filesByDirectory = nextFilesByDirectory;
|
|
45
|
+
for (const directory of nextDirectories)
|
|
46
|
+
this.trackDirectory(directory);
|
|
47
|
+
// A directory watcher is sufficient for file creation/rename on all supported
|
|
48
|
+
// platforms, but macOS does not reliably report content appends to an existing
|
|
49
|
+
// child. Watch known transcript files directly so resumed native sessions stay
|
|
50
|
+
// realtime; the periodic full scan remains the fallback when a handle fails.
|
|
51
|
+
for (const filePath of nextFiles)
|
|
52
|
+
this.trackFile(filePath);
|
|
53
|
+
}
|
|
54
|
+
/** Install the append watcher before parsing a newly discovered transcript. */
|
|
55
|
+
trackFile(filePath) {
|
|
56
|
+
const directory = path.dirname(filePath);
|
|
57
|
+
const files = this.filesByDirectory.get(directory) ?? new Set();
|
|
58
|
+
files.add(filePath);
|
|
59
|
+
this.filesByDirectory.set(directory, files);
|
|
60
|
+
this.trackDirectory(directory);
|
|
61
|
+
if (this.fileWatchers.has(filePath))
|
|
62
|
+
return;
|
|
63
|
+
try {
|
|
64
|
+
const watcher = this.fileWatchFactory(filePath, () => this.schedule(filePath));
|
|
65
|
+
this.fileWatchers.set(filePath, watcher);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.warn(`[native-fusion] unable to watch transcript ${filePath}`, error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
untrackFile(filePath) {
|
|
72
|
+
this.fileWatchers.get(filePath)?.close();
|
|
73
|
+
this.fileWatchers.delete(filePath);
|
|
74
|
+
const directory = path.dirname(filePath);
|
|
75
|
+
const files = this.filesByDirectory.get(directory);
|
|
76
|
+
files?.delete(filePath);
|
|
77
|
+
if (files?.size === 0)
|
|
78
|
+
this.filesByDirectory.delete(directory);
|
|
79
|
+
}
|
|
80
|
+
stop() {
|
|
81
|
+
for (const watcher of this.directoryWatchers.values())
|
|
82
|
+
watcher.close();
|
|
83
|
+
for (const watcher of this.fileWatchers.values())
|
|
84
|
+
watcher.close();
|
|
85
|
+
this.directoryWatchers.clear();
|
|
86
|
+
this.fileWatchers.clear();
|
|
87
|
+
this.filesByDirectory.clear();
|
|
88
|
+
for (const timer of this.debounceTimers.values())
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
this.debounceTimers.clear();
|
|
91
|
+
}
|
|
92
|
+
handleChange(directory, filename) {
|
|
93
|
+
const changedPath = filename ? path.join(directory, filename.toString()) : null;
|
|
94
|
+
if (changedPath) {
|
|
95
|
+
try {
|
|
96
|
+
const stat = fs.statSync(changedPath);
|
|
97
|
+
if (stat.isDirectory()) {
|
|
98
|
+
this.trackDirectoryTree(changedPath, true);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (stat.isFile() && isNativeTranscriptPath(changedPath)) {
|
|
102
|
+
this.trackFile(changedPath);
|
|
103
|
+
this.schedule(changedPath);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
if (isNativeTranscriptPath(changedPath)) {
|
|
109
|
+
this.untrackFile(changedPath);
|
|
110
|
+
this.schedule(changedPath);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const targets = [...(this.filesByDirectory.get(directory) ?? [])];
|
|
116
|
+
for (const target of targets)
|
|
117
|
+
this.schedule(target);
|
|
118
|
+
}
|
|
119
|
+
trackDirectory(directory) {
|
|
120
|
+
if (this.directoryWatchers.has(directory))
|
|
121
|
+
return;
|
|
122
|
+
try {
|
|
123
|
+
const watcher = this.directoryWatchFactory(directory, (_eventType, filename) => {
|
|
124
|
+
this.handleChange(directory, filename);
|
|
125
|
+
});
|
|
126
|
+
this.directoryWatchers.set(directory, watcher);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
console.warn(`[native-fusion] unable to watch ${directory}`, error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
collectDirectories(directory, output) {
|
|
133
|
+
if (!fs.existsSync(directory))
|
|
134
|
+
return;
|
|
135
|
+
let stat;
|
|
136
|
+
try {
|
|
137
|
+
stat = fs.statSync(directory);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (!stat.isDirectory())
|
|
143
|
+
return;
|
|
144
|
+
output.add(directory);
|
|
145
|
+
let entries;
|
|
146
|
+
try {
|
|
147
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
for (const entry of entries) {
|
|
153
|
+
if (entry.isDirectory())
|
|
154
|
+
this.collectDirectories(path.join(directory, entry.name), output);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
trackDirectoryTree(directory, scheduleDiscoveredFiles) {
|
|
158
|
+
this.trackDirectory(directory);
|
|
159
|
+
let entries;
|
|
160
|
+
try {
|
|
161
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
for (const entry of entries) {
|
|
167
|
+
const fullPath = path.join(directory, entry.name);
|
|
168
|
+
if (entry.isDirectory()) {
|
|
169
|
+
this.trackDirectoryTree(fullPath, scheduleDiscoveredFiles);
|
|
170
|
+
}
|
|
171
|
+
else if (entry.isFile() && isNativeTranscriptPath(fullPath)) {
|
|
172
|
+
this.trackFile(fullPath);
|
|
173
|
+
if (scheduleDiscoveredFiles)
|
|
174
|
+
this.schedule(fullPath);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
schedule(filePath) {
|
|
179
|
+
const previous = this.debounceTimers.get(filePath);
|
|
180
|
+
if (previous)
|
|
181
|
+
clearTimeout(previous);
|
|
182
|
+
const timer = setTimeout(() => {
|
|
183
|
+
this.debounceTimers.delete(filePath);
|
|
184
|
+
this.onChanged([filePath]);
|
|
185
|
+
}, WATCH_DEBOUNCE_MS);
|
|
186
|
+
this.debounceTimers.set(filePath, timer);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function isNativeTranscriptPath(filePath) {
|
|
190
|
+
return filePath.endsWith('.jsonl') || filePath.endsWith('.opencode-session.json');
|
|
191
|
+
}
|