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,3 +1,130 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// @arch docs/architecture/cli/native-session-fusion.md
|
|
2
|
+
// @test src/__tests__/native-fusion-parsers.test.ts
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { resolveBuiltinCommand, spawnResolvedCommandSync } from '../agents/command-spec.js';
|
|
7
|
+
import { normalizeNativeTimestamp } from './session-header.js';
|
|
8
|
+
function parseJsonObject(value) {
|
|
9
|
+
try {
|
|
10
|
+
const parsed = JSON.parse(value);
|
|
11
|
+
return typeof parsed === 'object' && parsed !== null ? parsed : null;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function textFromOpenCodeParts(parts) {
|
|
18
|
+
return parts
|
|
19
|
+
.filter((part) => part.type === 'text' || part.type === 'reasoning')
|
|
20
|
+
.map((part) => typeof part.text === 'string' ? part.text : '')
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
.join('\n\n')
|
|
23
|
+
.trim();
|
|
24
|
+
}
|
|
25
|
+
function pushOpenCodeEvent(events, snapshotPath, session, message, role, payload, modelId, terminal) {
|
|
26
|
+
if (!payload)
|
|
27
|
+
return;
|
|
28
|
+
const eventTimestamp = normalizeNativeTimestamp(message.time_created);
|
|
29
|
+
const sourceSessionCreatedAtMs = normalizeNativeTimestamp(session.time_created);
|
|
30
|
+
if (!eventTimestamp || !sourceSessionCreatedAtMs)
|
|
31
|
+
return;
|
|
32
|
+
events.push({
|
|
33
|
+
agentType: 'opencode',
|
|
34
|
+
sourceMode: 'opencode_session_import',
|
|
35
|
+
sourceSessionKey: session.id,
|
|
36
|
+
sourceEventKey: `opencode:${session.id}:${message.id}:${role}`,
|
|
37
|
+
cursor: `${snapshotPath}:${message.time_created}:${message.id}`,
|
|
38
|
+
role,
|
|
39
|
+
ts: eventTimestamp,
|
|
40
|
+
payload,
|
|
41
|
+
title: session.title,
|
|
42
|
+
titleSource: 'agent_native',
|
|
43
|
+
modelId,
|
|
44
|
+
workDir: session.directory,
|
|
45
|
+
terminal,
|
|
46
|
+
sourceSessionCreatedAtMs,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function parseOpenCodeSessionSnapshot(snapshotPath, snapshot) {
|
|
50
|
+
const events = [];
|
|
51
|
+
const sessionsById = new Map(snapshot.sessions.map((session) => [session.id, session]));
|
|
52
|
+
const partsByMessage = new Map();
|
|
53
|
+
for (const partRow of snapshot.parts) {
|
|
54
|
+
const data = parseJsonObject(partRow.data);
|
|
55
|
+
if (!data)
|
|
56
|
+
continue;
|
|
57
|
+
const list = partsByMessage.get(partRow.message_id) ?? [];
|
|
58
|
+
list.push(data);
|
|
59
|
+
partsByMessage.set(partRow.message_id, list);
|
|
60
|
+
}
|
|
61
|
+
for (const message of [...snapshot.messages].sort((left, right) => left.time_created - right.time_created)) {
|
|
62
|
+
const session = sessionsById.get(message.session_id);
|
|
63
|
+
if (!session)
|
|
64
|
+
continue;
|
|
65
|
+
const data = parseJsonObject(message.data);
|
|
66
|
+
if (!data)
|
|
67
|
+
continue;
|
|
68
|
+
const role = data.role === 'user' ? 'user' : data.role === 'assistant' ? 'agent' : null;
|
|
69
|
+
if (!role)
|
|
70
|
+
continue;
|
|
71
|
+
const parts = partsByMessage.get(message.id) ?? [];
|
|
72
|
+
const payload = textFromOpenCodeParts(parts);
|
|
73
|
+
const modelId = typeof data.modelID === 'string'
|
|
74
|
+
? `${typeof data.providerID === 'string' ? `${data.providerID}/` : ''}${data.modelID}`
|
|
75
|
+
: null;
|
|
76
|
+
pushOpenCodeEvent(events, snapshotPath, session, message, role, payload, modelId, role === 'agent');
|
|
77
|
+
}
|
|
78
|
+
return events;
|
|
79
|
+
}
|
|
80
|
+
export function listOpenCodeSessionFiles() {
|
|
81
|
+
const dataDir = path.join(os.homedir(), '.local', 'share', 'opencode');
|
|
82
|
+
const snapshot = path.join(dataDir, 'shennian-opencode-session-snapshot.opencode-session.json');
|
|
83
|
+
refreshOpenCodeSessionSnapshot(snapshot);
|
|
84
|
+
return fs.existsSync(snapshot) ? [snapshot] : [];
|
|
85
|
+
}
|
|
86
|
+
function queryOpenCodeDb(query) {
|
|
87
|
+
const spec = resolveBuiltinCommand('opencode');
|
|
88
|
+
if (!spec)
|
|
89
|
+
return [];
|
|
90
|
+
const result = spawnResolvedCommandSync(spec, ['db', query, '--format', 'json'], {
|
|
91
|
+
encoding: 'utf-8',
|
|
92
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
93
|
+
timeout: 10_000,
|
|
94
|
+
});
|
|
95
|
+
if (result.status !== 0)
|
|
96
|
+
return [];
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(String(result.stdout ?? ''));
|
|
99
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function refreshOpenCodeSessionSnapshot(snapshotPath) {
|
|
106
|
+
try {
|
|
107
|
+
const sessions = queryOpenCodeDb('select id, title, directory, time_created, time_updated from session where time_archived is null order by time_updated asc');
|
|
108
|
+
if (sessions.length === 0)
|
|
109
|
+
return;
|
|
110
|
+
const messages = queryOpenCodeDb('select id, session_id, time_created, data from message order by time_created asc, id asc');
|
|
111
|
+
const parts = queryOpenCodeDb('select id, message_id, session_id, time_created, data from part order by message_id asc, id asc');
|
|
112
|
+
fs.mkdirSync(path.dirname(snapshotPath), { recursive: true });
|
|
113
|
+
fs.writeFileSync(snapshotPath, JSON.stringify({ sessions, messages, parts }));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
/* best-effort native import */
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export function parseOpenCodeSessionFile(filePath, startOffset) {
|
|
120
|
+
const stat = fs.statSync(filePath);
|
|
121
|
+
if (startOffset >= stat.size)
|
|
122
|
+
return { nextOffset: stat.size, events: [] };
|
|
123
|
+
if (startOffset > 0)
|
|
124
|
+
return { nextOffset: stat.size, events: [] };
|
|
125
|
+
const snapshot = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
126
|
+
return {
|
|
127
|
+
nextOffset: stat.size,
|
|
128
|
+
events: parseOpenCodeSessionSnapshot(filePath, snapshot),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -1,3 +1,140 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// @arch docs/architecture/cli/native-session-fusion.md
|
|
2
|
+
// @test src/__tests__/native-fusion-parsers.test.ts
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
export { listCodexRolloutFiles, lookupCodexThreadName, parseCodexRolloutChunk, } from './codex-parser.js';
|
|
7
|
+
export { inspectNativeSessionFile, normalizeNativeTimestamp } from './session-header.js';
|
|
8
|
+
export { listOpenCodeSessionFiles, parseOpenCodeSessionFile, parseOpenCodeSessionSnapshot, } from './opencode-parser.js';
|
|
9
|
+
import { makeCursor, makeEventKey, normalizeText, readClaudeEventCwd, readJsonlLines, readTimestamp, safeParse, isClaudeSubagentTranscript, shouldIgnoreClaudeProjectDir, shouldIgnoreClaudeTranscriptPath, shouldIgnoreNativeScanPath, } from './parser-common.js';
|
|
10
|
+
export function listNativeTranscriptRoots() {
|
|
11
|
+
return [
|
|
12
|
+
path.join(os.homedir(), '.codex', 'sessions'),
|
|
13
|
+
path.join(os.homedir(), '.codex', 'archived_sessions'),
|
|
14
|
+
path.join(os.homedir(), '.claude', 'projects'),
|
|
15
|
+
path.join(os.homedir(), '.local', 'share', 'opencode'),
|
|
16
|
+
];
|
|
17
|
+
}
|
|
18
|
+
function relativeProjectDir(cwd) {
|
|
19
|
+
const home = os.homedir();
|
|
20
|
+
if (cwd.startsWith(home))
|
|
21
|
+
return cwd.slice(home.length).replace(/^\//, '') || '~';
|
|
22
|
+
return cwd.replace(/\//g, '-');
|
|
23
|
+
}
|
|
24
|
+
export function listClaudeTranscriptFiles() {
|
|
25
|
+
const root = path.join(os.homedir(), '.claude', 'projects');
|
|
26
|
+
if (!fs.existsSync(root))
|
|
27
|
+
return [];
|
|
28
|
+
const files = [];
|
|
29
|
+
const walk = (dir) => {
|
|
30
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
31
|
+
const full = path.join(dir, entry.name);
|
|
32
|
+
if (entry.isDirectory()) {
|
|
33
|
+
if (dir === root && shouldIgnoreClaudeProjectDir(entry.name))
|
|
34
|
+
continue;
|
|
35
|
+
walk(full);
|
|
36
|
+
}
|
|
37
|
+
else if (entry.isFile() && entry.name.endsWith('.jsonl'))
|
|
38
|
+
files.push(full);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
walk(root);
|
|
42
|
+
return files.sort();
|
|
43
|
+
}
|
|
44
|
+
export function lookupClaudeTranscriptCwd(sourceSessionKey) {
|
|
45
|
+
if (!sourceSessionKey)
|
|
46
|
+
return null;
|
|
47
|
+
const match = listClaudeTranscriptFiles().find((filePath) => path.basename(filePath, '.jsonl') === sourceSessionKey);
|
|
48
|
+
if (!match)
|
|
49
|
+
return null;
|
|
50
|
+
let found = null;
|
|
51
|
+
readJsonlLines(match, 0, (line) => {
|
|
52
|
+
if (found || !line.trim())
|
|
53
|
+
return;
|
|
54
|
+
const parsed = safeParse(line);
|
|
55
|
+
if (!parsed)
|
|
56
|
+
return;
|
|
57
|
+
const cwd = readClaudeEventCwd(parsed);
|
|
58
|
+
if (cwd)
|
|
59
|
+
found = cwd;
|
|
60
|
+
});
|
|
61
|
+
return found;
|
|
62
|
+
}
|
|
63
|
+
export function parseClaudeTranscriptChunk(filePath, startOffset) {
|
|
64
|
+
const fileSize = fs.statSync(filePath).size;
|
|
65
|
+
if (isClaudeSubagentTranscript(filePath) || shouldIgnoreClaudeTranscriptPath(filePath)) {
|
|
66
|
+
return { nextOffset: fileSize, events: [] };
|
|
67
|
+
}
|
|
68
|
+
const events = [];
|
|
69
|
+
const sourceSessionKey = path.basename(filePath, '.jsonl');
|
|
70
|
+
const fallbackWorkDir = relativeProjectDir(path.dirname(filePath).replace(path.join(os.homedir(), '.claude', 'projects') + path.sep, ''));
|
|
71
|
+
if (shouldIgnoreNativeScanPath(fallbackWorkDir)) {
|
|
72
|
+
return { nextOffset: fileSize, events: [] };
|
|
73
|
+
}
|
|
74
|
+
let title = '';
|
|
75
|
+
const nextOffset = readJsonlLines(filePath, startOffset, (line, lineOffset) => {
|
|
76
|
+
const parsed = safeParse(line);
|
|
77
|
+
if (!parsed)
|
|
78
|
+
return;
|
|
79
|
+
const ts = readTimestamp(parsed.timestamp);
|
|
80
|
+
const type = typeof parsed.type === 'string' ? parsed.type : '';
|
|
81
|
+
if (!ts || !type)
|
|
82
|
+
return;
|
|
83
|
+
const eventCwd = readClaudeEventCwd(parsed);
|
|
84
|
+
if (eventCwd && shouldIgnoreNativeScanPath(eventCwd))
|
|
85
|
+
return;
|
|
86
|
+
if (type === 'user') {
|
|
87
|
+
const message = typeof parsed.message === 'object' && parsed.message !== null
|
|
88
|
+
? parsed.message
|
|
89
|
+
: null;
|
|
90
|
+
const text = typeof message?.content === 'string' ? normalizeText(message.content) : '';
|
|
91
|
+
if (!text)
|
|
92
|
+
return;
|
|
93
|
+
if (!title)
|
|
94
|
+
title = text.slice(0, 80);
|
|
95
|
+
events.push({
|
|
96
|
+
agentType: 'claude',
|
|
97
|
+
sourceMode: 'claude_transcript_import',
|
|
98
|
+
sourceSessionKey,
|
|
99
|
+
sourceEventKey: makeEventKey(filePath, lineOffset, 'user'),
|
|
100
|
+
cursor: makeCursor(filePath, lineOffset),
|
|
101
|
+
role: 'user',
|
|
102
|
+
ts,
|
|
103
|
+
payload: text,
|
|
104
|
+
title,
|
|
105
|
+
titleSource: 'first_user_fallback',
|
|
106
|
+
workDir: eventCwd ?? fallbackWorkDir,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else if (type === 'assistant') {
|
|
110
|
+
const message = typeof parsed.message === 'object' && parsed.message !== null
|
|
111
|
+
? parsed.message
|
|
112
|
+
: null;
|
|
113
|
+
const content = Array.isArray(message?.content) ? message.content : [];
|
|
114
|
+
const text = normalizeText(content
|
|
115
|
+
.map((part) => typeof part === 'object' && part !== null && typeof part.text === 'string'
|
|
116
|
+
? part.text
|
|
117
|
+
: '')
|
|
118
|
+
.filter(Boolean)
|
|
119
|
+
.join('\n\n'));
|
|
120
|
+
if (!text)
|
|
121
|
+
return;
|
|
122
|
+
const modelId = typeof message?.model === 'string' ? message.model : null;
|
|
123
|
+
events.push({
|
|
124
|
+
agentType: 'claude',
|
|
125
|
+
sourceMode: 'claude_transcript_import',
|
|
126
|
+
sourceSessionKey,
|
|
127
|
+
sourceEventKey: makeEventKey(filePath, lineOffset, 'assistant'),
|
|
128
|
+
cursor: makeCursor(filePath, lineOffset),
|
|
129
|
+
role: 'agent',
|
|
130
|
+
ts,
|
|
131
|
+
payload: text,
|
|
132
|
+
title,
|
|
133
|
+
titleSource: 'first_user_fallback',
|
|
134
|
+
modelId,
|
|
135
|
+
workDir: eventCwd ?? fallbackWorkDir,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
return { nextOffset, events };
|
|
140
|
+
}
|