shennian 0.2.88 → 0.2.90
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/assets/wechat-channel/macos/manifest.json +22 -0
- package/dist/assets/wechat-channel/macos/shennian-wechat-channel-helper +0 -0
- package/dist/bin/shennian.js +1 -1
- package/dist/publish-build-manifest.json +548 -0
- package/dist/scripts/wechat-rpa-confirmation.mjs +5 -97
- package/dist/src/agent-env.js +4 -105
- package/dist/src/agents/adapter.d.ts +6 -0
- package/dist/src/agents/adapter.js +1 -19
- package/dist/src/agents/claude.js +8 -305
- package/dist/src/agents/codex-control.d.ts +35 -0
- package/dist/src/agents/codex-control.js +2 -0
- package/dist/src/agents/codex-utils.js +7 -200
- package/dist/src/agents/codex.d.ts +8 -0
- package/dist/src/agents/codex.js +15 -863
- package/dist/src/agents/command-spec.js +2 -413
- package/dist/src/agents/config-status.js +1 -226
- package/dist/src/agents/cursor.js +1 -249
- package/dist/src/agents/custom.js +4 -271
- package/dist/src/agents/detect.js +1 -56
- package/dist/src/agents/external-channel-instructions.js +10 -94
- package/dist/src/agents/gemini.js +1 -173
- package/dist/src/agents/manager.js +13 -157
- package/dist/src/agents/model-registry/cache.js +1 -37
- package/dist/src/agents/model-registry/discovery.js +2 -187
- package/dist/src/agents/model-registry/parsers.js +4 -447
- package/dist/src/agents/model-registry/runner.js +1 -30
- package/dist/src/agents/model-registry/service.js +1 -78
- package/dist/src/agents/model-registry/types.js +1 -8
- package/dist/src/agents/model-registry.js +1 -18
- package/dist/src/agents/openclaw.js +2 -275
- package/dist/src/agents/opencode.js +1 -231
- package/dist/src/agents/pi-context.js +12 -217
- package/dist/src/agents/pi.js +14 -723
- package/dist/src/agents/platform-instructions.js +9 -54
- package/dist/src/channels/base.d.ts +4 -1
- package/dist/src/channels/base.js +1 -3
- package/dist/src/channels/registry.js +1 -30
- package/dist/src/channels/reply-split.js +10 -89
- package/dist/src/channels/runtime.d.ts +1 -0
- package/dist/src/channels/runtime.js +5 -533
- package/dist/src/channels/secret-registry.d.ts +1 -0
- package/dist/src/channels/secret-registry.js +1 -46
- package/dist/src/channels/websocket.js +8 -378
- package/dist/src/channels/wechat-channel/anchor.d.ts +10 -0
- package/dist/src/channels/wechat-channel/anchor.js +1 -0
- package/dist/src/channels/wechat-channel/client.d.ts +74 -0
- package/dist/src/channels/wechat-channel/client.js +1 -0
- package/dist/src/channels/wechat-channel/cooldown.d.ts +15 -0
- package/dist/src/channels/wechat-channel/cooldown.js +1 -0
- package/dist/src/channels/wechat-channel/fingerprint.d.ts +28 -0
- package/dist/src/channels/wechat-channel/fingerprint.js +1 -0
- package/dist/src/channels/wechat-channel/helper-assets.d.ts +37 -0
- package/dist/src/channels/wechat-channel/helper-assets.js +1 -0
- package/dist/src/channels/wechat-channel/helper-client.d.ts +25 -0
- package/dist/src/channels/wechat-channel/helper-client.js +3 -0
- package/dist/src/channels/wechat-channel/helper-protocol.d.ts +84 -0
- package/dist/src/channels/wechat-channel/helper-protocol.js +1 -0
- package/dist/src/channels/wechat-channel/index.d.ts +17 -0
- package/dist/src/channels/wechat-channel/index.js +1 -0
- package/dist/src/channels/wechat-channel/ledger.d.ts +33 -0
- package/dist/src/channels/wechat-channel/ledger.js +1 -0
- package/dist/src/channels/wechat-channel/media-resolver.d.ts +32 -0
- package/dist/src/channels/wechat-channel/media-resolver.js +1 -0
- package/dist/src/channels/wechat-channel/message-key.d.ts +19 -0
- package/dist/src/channels/wechat-channel/message-key.js +1 -0
- package/dist/src/channels/wechat-channel/observer.d.ts +64 -0
- package/dist/src/channels/wechat-channel/observer.js +1 -0
- package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +69 -0
- package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -0
- package/dist/src/channels/wechat-channel/outbound-sender.d.ts +26 -0
- package/dist/src/channels/wechat-channel/outbound-sender.js +1 -0
- package/dist/src/channels/wechat-channel/preflight.d.ts +37 -0
- package/dist/src/channels/wechat-channel/preflight.js +1 -0
- package/dist/src/channels/wechat-channel/runner.d.ts +34 -0
- package/dist/src/channels/wechat-channel/runner.js +1 -0
- package/dist/src/channels/wechat-channel/runtime.d.ts +45 -0
- package/dist/src/channels/wechat-channel/runtime.js +1 -0
- package/dist/src/channels/wechat-channel/scheduler.d.ts +35 -0
- package/dist/src/channels/wechat-channel/scheduler.js +1 -0
- package/dist/src/channels/wechat-rpa/macos-flow.js +1 -96
- package/dist/src/channels/wechat-rpa/macos.js +6 -48
- package/dist/src/channels/wechat-rpa/normalizer.js +7 -127
- package/dist/src/channels/wechat-rpa.d.ts +21 -0
- package/dist/src/channels/wechat-rpa.js +6 -1022
- package/dist/src/channels/wecom.js +4 -357
- package/dist/src/commands/agent.js +6 -131
- package/dist/src/commands/daemon-windows.js +8 -48
- package/dist/src/commands/daemon.js +19 -1013
- package/dist/src/commands/external-attachments.js +1 -51
- package/dist/src/commands/external.js +1 -137
- package/dist/src/commands/manager.js +2 -389
- package/dist/src/commands/pair-qr.js +1 -6
- package/dist/src/commands/pair.js +9 -287
- package/dist/src/commands/tools.js +1 -34
- package/dist/src/commands/upgrade.js +1 -198
- package/dist/src/config/index.js +1 -35
- package/dist/src/daemon-log.js +6 -58
- package/dist/src/env-path.js +1 -64
- package/dist/src/fs/boundary.js +1 -126
- package/dist/src/fs/handler.js +1 -130
- package/dist/src/fs/security.js +1 -32
- package/dist/src/fs/text-decoder.d.ts +10 -0
- package/dist/src/fs/text-decoder.js +1 -0
- package/dist/src/index.js +2 -404
- package/dist/src/log-reporter.js +1 -16
- package/dist/src/manager/prompt.js +29 -34
- package/dist/src/manager/registry.js +2 -269
- package/dist/src/manager/runtime.js +19 -1003
- package/dist/src/native-fusion/config.js +1 -5
- package/dist/src/native-fusion/opencode-parser.js +3 -123
- package/dist/src/native-fusion/parser-common.js +8 -264
- package/dist/src/native-fusion/parsers.js +8 -729
- package/dist/src/native-fusion/service.d.ts +10 -0
- package/dist/src/native-fusion/service.js +2 -198
- package/dist/src/native-fusion/state.js +1 -22
- package/dist/src/native-fusion/types.js +1 -1
- package/dist/src/region.js +1 -88
- package/dist/src/relay/client.js +1 -343
- package/dist/src/session/archive-zip.js +1 -220
- 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 -733
- package/dist/src/session/handlers/control.js +1 -55
- package/dist/src/session/handlers/fs.js +1 -747
- package/dist/src/session/handlers/session-refresh.js +1 -35
- package/dist/src/session/handlers/skills.js +1 -121
- package/dist/src/session/handlers/title.js +1 -60
- package/dist/src/session/handlers/tool-detail.d.ts +3 -0
- package/dist/src/session/handlers/tool-detail.js +1 -0
- package/dist/src/session/manager.d.ts +3 -0
- package/dist/src/session/manager.js +1 -261
- package/dist/src/session/projection.js +1 -54
- package/dist/src/session/queue.js +4 -317
- package/dist/src/session/remote-attachments.js +1 -72
- package/dist/src/session/store.js +3 -109
- package/dist/src/session/types.d.ts +4 -0
- package/dist/src/session/types.js +1 -4
- package/dist/src/skills/registry.js +15 -148
- package/dist/src/skills/setup.js +1 -101
- package/dist/src/tools/markdown-to-pdf.js +10 -346
- package/dist/src/upgrade/engine.js +3 -347
- package/package.json +3 -2
- package/dist/scripts/wechat-rpa-download-candidates.mjs +0 -105
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CliRelayClient } from '../relay/client.js';
|
|
2
|
+
import type { SessionActivitySnapshot } from '@shennian/wire';
|
|
2
3
|
export declare class NativeSessionFusionService {
|
|
3
4
|
private client;
|
|
4
5
|
private timer;
|
|
@@ -20,6 +21,15 @@ export declare class NativeSessionFusionService {
|
|
|
20
21
|
noteManagedSourceSession(sessionId: string, agentType: string, sourceSessionKey: string | null): void;
|
|
21
22
|
private pruneState;
|
|
22
23
|
scanNow(): Promise<void>;
|
|
24
|
+
getCodexThreadActivity(params: {
|
|
25
|
+
sessionId: string;
|
|
26
|
+
threadId: string;
|
|
27
|
+
workDir?: string;
|
|
28
|
+
}): Promise<SessionActivitySnapshot | null | undefined>;
|
|
29
|
+
interruptCodexThread(params: {
|
|
30
|
+
threadId: string;
|
|
31
|
+
workDir?: string;
|
|
32
|
+
}): Promise<boolean>;
|
|
23
33
|
private runScan;
|
|
24
34
|
private tryClaimManagedEcho;
|
|
25
35
|
private isSuppressed;
|
|
@@ -1,198 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import fs from 'node:fs';
|
|
5
|
-
import { listClaudeTranscriptFiles, listCodexRolloutFiles, listOpenCodeSessionFiles, parseClaudeTranscriptChunk, parseCodexRolloutChunk, parseOpenCodeSessionFile, } from './parsers.js';
|
|
6
|
-
import { loadNativeScannerState, saveNativeScannerState } from './state.js';
|
|
7
|
-
const SCAN_INTERVAL_MS = 60_000;
|
|
8
|
-
const CLAIM_TTL_MS = SCAN_INTERVAL_MS * 2;
|
|
9
|
-
const MANAGED_RUN_SUPPRESSION_TTL_MS = 30 * 60_000;
|
|
10
|
-
const MAX_BATCH_SIZE = 20;
|
|
11
|
-
function normalizeText(text) {
|
|
12
|
-
return text.replace(/\r\n/g, '\n').trim();
|
|
13
|
-
}
|
|
14
|
-
function isCodexRolloutPath(filePath) {
|
|
15
|
-
return filePath.split(/[\\/]+/).includes('.codex');
|
|
16
|
-
}
|
|
17
|
-
export class NativeSessionFusionService {
|
|
18
|
-
client;
|
|
19
|
-
timer = null;
|
|
20
|
-
scanPromise = null;
|
|
21
|
-
pendingClaims = new Map();
|
|
22
|
-
suppressionWindows = new Map();
|
|
23
|
-
constructor(client) {
|
|
24
|
-
this.client = client;
|
|
25
|
-
}
|
|
26
|
-
start() {
|
|
27
|
-
if (this.timer)
|
|
28
|
-
return;
|
|
29
|
-
this.timer = setInterval(() => {
|
|
30
|
-
void this.scanNow().catch((error) => {
|
|
31
|
-
console.error('[native-fusion] periodic scan failed', error);
|
|
32
|
-
});
|
|
33
|
-
}, SCAN_INTERVAL_MS);
|
|
34
|
-
}
|
|
35
|
-
stop() {
|
|
36
|
-
if (this.timer)
|
|
37
|
-
clearInterval(this.timer);
|
|
38
|
-
this.timer = null;
|
|
39
|
-
}
|
|
40
|
-
handleConnected() {
|
|
41
|
-
this.start();
|
|
42
|
-
void this.scanNow().catch((error) => {
|
|
43
|
-
console.error('[native-fusion] initial scan failed', error);
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
registerManagedSend(params) {
|
|
47
|
-
if (!params.canonicalMessageId)
|
|
48
|
-
return;
|
|
49
|
-
const sourceAgentType = params.sourceAgentType ?? params.agentType;
|
|
50
|
-
if (sourceAgentType !== 'codex' && sourceAgentType !== 'claude' && sourceAgentType !== 'opencode')
|
|
51
|
-
return;
|
|
52
|
-
this.pendingClaims.set(params.sessionId, {
|
|
53
|
-
sessionId: params.sessionId,
|
|
54
|
-
agentType: params.agentType,
|
|
55
|
-
sourceAgentType,
|
|
56
|
-
canonicalMessageId: params.canonicalMessageId,
|
|
57
|
-
text: normalizeText(params.text),
|
|
58
|
-
createdAt: Date.now(),
|
|
59
|
-
expiresAt: Date.now() + CLAIM_TTL_MS,
|
|
60
|
-
sourceSessionKey: params.sourceSessionKey ?? null,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
noteManagedSourceSession(sessionId, agentType, sourceSessionKey) {
|
|
64
|
-
if (!sourceSessionKey)
|
|
65
|
-
return;
|
|
66
|
-
const claim = this.pendingClaims.get(sessionId);
|
|
67
|
-
if (claim && claim.sourceAgentType === agentType) {
|
|
68
|
-
claim.sourceSessionKey = sourceSessionKey;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
pruneState() {
|
|
72
|
-
const now = Date.now();
|
|
73
|
-
for (const [sessionId, claim] of this.pendingClaims.entries()) {
|
|
74
|
-
if (claim.expiresAt <= now)
|
|
75
|
-
this.pendingClaims.delete(sessionId);
|
|
76
|
-
}
|
|
77
|
-
for (const [key, window] of this.suppressionWindows.entries()) {
|
|
78
|
-
if (window.endsAt <= now)
|
|
79
|
-
this.suppressionWindows.delete(key);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
async scanNow() {
|
|
83
|
-
if (this.client.getState() !== 'connected')
|
|
84
|
-
return;
|
|
85
|
-
if (this.scanPromise)
|
|
86
|
-
return this.scanPromise;
|
|
87
|
-
this.scanPromise = this.runScan().finally(() => {
|
|
88
|
-
this.scanPromise = null;
|
|
89
|
-
});
|
|
90
|
-
return this.scanPromise;
|
|
91
|
-
}
|
|
92
|
-
async runScan() {
|
|
93
|
-
this.pruneState();
|
|
94
|
-
const state = loadNativeScannerState();
|
|
95
|
-
const nextFilesState = { ...state.files };
|
|
96
|
-
const batches = [];
|
|
97
|
-
const files = [...listCodexRolloutFiles(), ...listClaudeTranscriptFiles(), ...listOpenCodeSessionFiles()];
|
|
98
|
-
for (const filePath of files) {
|
|
99
|
-
const stat = fs.statSync(filePath);
|
|
100
|
-
const current = state.files[filePath];
|
|
101
|
-
if (!current) {
|
|
102
|
-
nextFilesState[filePath] = {
|
|
103
|
-
offset: stat.size,
|
|
104
|
-
mtimeMs: stat.mtimeMs,
|
|
105
|
-
};
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
const isCodex = isCodexRolloutPath(filePath);
|
|
109
|
-
const isOpenCode = filePath.endsWith('.opencode-session.json');
|
|
110
|
-
const startOffset = isOpenCode && current?.mtimeMs !== stat.mtimeMs
|
|
111
|
-
? current.offset
|
|
112
|
-
: current?.offset ?? 0;
|
|
113
|
-
const parsed = isOpenCode
|
|
114
|
-
? parseOpenCodeSessionFile(filePath, startOffset)
|
|
115
|
-
: isCodex
|
|
116
|
-
? parseCodexRolloutChunk(filePath, startOffset)
|
|
117
|
-
: parseClaudeTranscriptChunk(filePath, startOffset);
|
|
118
|
-
nextFilesState[filePath] = {
|
|
119
|
-
offset: parsed.nextOffset,
|
|
120
|
-
mtimeMs: stat.mtimeMs,
|
|
121
|
-
};
|
|
122
|
-
for (const event of parsed.events) {
|
|
123
|
-
const claimed = this.tryClaimManagedEcho(event);
|
|
124
|
-
if (claimed) {
|
|
125
|
-
batches.push(claimed);
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
if (this.isSuppressed(event))
|
|
129
|
-
continue;
|
|
130
|
-
batches.push(event);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
for (let index = 0; index < batches.length; index += MAX_BATCH_SIZE) {
|
|
134
|
-
const chunk = batches.slice(index, index + MAX_BATCH_SIZE);
|
|
135
|
-
if (chunk.length === 0)
|
|
136
|
-
continue;
|
|
137
|
-
const wireEvents = chunk.map(({ repairHint: _repairHint, ...event }) => event);
|
|
138
|
-
await this.client.sendBufferedEvent({
|
|
139
|
-
type: 'event',
|
|
140
|
-
event: 'native.session.events',
|
|
141
|
-
id: `native-fusion-${randomUUID()}`,
|
|
142
|
-
payload: { events: wireEvents },
|
|
143
|
-
}, 60_000);
|
|
144
|
-
}
|
|
145
|
-
state.files = nextFilesState;
|
|
146
|
-
saveNativeScannerState(state);
|
|
147
|
-
}
|
|
148
|
-
tryClaimManagedEcho(event) {
|
|
149
|
-
const now = Date.now();
|
|
150
|
-
for (const [sessionId, claim] of this.pendingClaims.entries()) {
|
|
151
|
-
if (claim.sourceAgentType !== event.agentType)
|
|
152
|
-
continue;
|
|
153
|
-
if (claim.expiresAt <= now)
|
|
154
|
-
continue;
|
|
155
|
-
if (event.role !== 'user')
|
|
156
|
-
continue;
|
|
157
|
-
if (!claim.sourceSessionKey)
|
|
158
|
-
continue;
|
|
159
|
-
if (claim.sourceSessionKey !== event.sourceSessionKey)
|
|
160
|
-
continue;
|
|
161
|
-
if (Math.abs(event.ts - claim.createdAt) > CLAIM_TTL_MS)
|
|
162
|
-
continue;
|
|
163
|
-
if (normalizeText(event.payload) !== claim.text)
|
|
164
|
-
continue;
|
|
165
|
-
this.pendingClaims.delete(sessionId);
|
|
166
|
-
this.suppressionWindows.set(`${event.agentType}:${event.sourceSessionKey}`, {
|
|
167
|
-
sessionId,
|
|
168
|
-
agentType: event.agentType,
|
|
169
|
-
sourceSessionKey: event.sourceSessionKey,
|
|
170
|
-
startedAt: event.ts,
|
|
171
|
-
endsAt: Math.max(now, event.ts) + MANAGED_RUN_SUPPRESSION_TTL_MS,
|
|
172
|
-
});
|
|
173
|
-
return {
|
|
174
|
-
...event,
|
|
175
|
-
sourceMode: 'managed_echo_claim',
|
|
176
|
-
claimCanonicalMessageId: claim.canonicalMessageId,
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
isSuppressed(event) {
|
|
182
|
-
const key = `${event.agentType}:${event.sourceSessionKey}`;
|
|
183
|
-
const window = this.suppressionWindows.get(key);
|
|
184
|
-
if (!window)
|
|
185
|
-
return false;
|
|
186
|
-
if (event.ts < window.startedAt)
|
|
187
|
-
return false;
|
|
188
|
-
if (event.ts > window.endsAt) {
|
|
189
|
-
this.suppressionWindows.delete(key);
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
if (event.role === 'user') {
|
|
193
|
-
this.suppressionWindows.delete(key);
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
return true;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
1
|
+
import{randomUUID as g}from"node:crypto";import w from"node:fs";import{listClaudeTranscriptFiles as C,listCodexRolloutFiles as I,listOpenCodeSessionFiles as M,parseClaudeTranscriptChunk as A,parseCodexRolloutChunk as T,parseOpenCodeSessionFile as x}from"./parsers.js";import{probeCodexThreadActivity as _,interruptCodexThread as P}from"../agents/codex-control.js";import{loadNativeScannerState as v,saveNativeScannerState as D}from"./state.js";const h=6e4,m=h*2,N=30*6e4,S=20;function y(a){return a.replace(/\r\n/g,`
|
|
2
|
+
`).trim()}function k(a){return a.split(/[\\/]+/).includes(".codex")}class R{client;timer=null;scanPromise=null;pendingClaims=new Map;suppressionWindows=new Map;constructor(e){this.client=e}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},h))}stop(){this.timer&&clearInterval(this.timer),this.timer=null}handleConnected(){this.start(),this.scanNow().catch(e=>{console.error("[native-fusion] initial scan failed",e)})}registerManagedSend(e){if(!e.canonicalMessageId)return;const s=e.sourceAgentType??e.agentType;s!=="codex"&&s!=="claude"&&s!=="opencode"||this.pendingClaims.set(e.sessionId,{sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,text:y(e.text),createdAt:Date.now(),expiresAt:Date.now()+m,sourceSessionKey:e.sourceSessionKey??null})}noteManagedSourceSession(e,s,t){if(!t)return;const i=this.pendingClaims.get(e);i&&i.sourceAgentType===s&&(i.sourceSessionKey=t)}pruneState(){const e=Date.now();for(const[s,t]of this.pendingClaims.entries())t.expiresAt<=e&&this.pendingClaims.delete(s);for(const[s,t]of this.suppressionWindows.entries())t.endsAt<=e&&this.suppressionWindows.delete(s)}async scanNow(){if(this.client.getState()==="connected")return this.scanPromise?this.scanPromise:(this.scanPromise=this.runScan().finally(()=>{this.scanPromise=null}),this.scanPromise)}async getCodexThreadActivity(e){const s=await _({threadId:e.threadId,workDir:e.workDir});if(!s)return;if(!s.active||!s.runPhase)return null;const t=new Date().toISOString();return{sessionId:e.sessionId,runId:s.turnId??`codex:${e.threadId}`,runPhase:s.runPhase,startedAt:t,updatedAt:t,canStop:s.canStop}}async interruptCodexThread(e){return!!await P({threadId:e.threadId,workDir:e.workDir})}async runScan(){this.pruneState();const e=v(),s={...e.files},t=[],i=[...I(),...C(),...M()];for(const n of i){const o=w.statSync(n),r=e.files[n];if(!r){s[n]={offset:o.size,mtimeMs:o.mtimeMs};continue}const l=k(n),c=n.endsWith(".opencode-session.json"),d=c&&r?.mtimeMs!==o.mtimeMs?r.offset:r?.offset??0,f=c?x(n,d):l?T(n,d):A(n,d);s[n]={offset:f.nextOffset,mtimeMs:o.mtimeMs};for(const u of f.events){const p=this.tryClaimManagedEcho(u);if(p){t.push(p);continue}this.isSuppressed(u)||t.push(u)}}for(let n=0;n<t.length;n+=S){const o=t.slice(n,n+S);if(o.length===0)continue;const r=o.map(({repairHint:l,...c})=>c);await this.client.sendBufferedEvent({type:"event",event:"native.session.events",id:`native-fusion-${g()}`,payload:{events:r}},6e4)}e.files=s,D(e)}tryClaimManagedEcho(e){const s=Date.now();for(const[t,i]of this.pendingClaims.entries())if(i.sourceAgentType===e.agentType&&!(i.expiresAt<=s)&&e.role==="user"&&i.sourceSessionKey&&i.sourceSessionKey===e.sourceSessionKey&&!(Math.abs(e.ts-i.createdAt)>m)&&y(e.payload)===i.text)return this.pendingClaims.delete(t),this.suppressionWindows.set(`${e.agentType}:${e.sourceSessionKey}`,{sessionId:t,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,startedAt:e.ts,endsAt:Math.max(s,e.ts)+N}),{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:i.canonicalMessageId};return null}isSuppressed(e){const s=`${e.agentType}:${e.sourceSessionKey}`,t=this.suppressionWindows.get(s);return!t||e.ts<t.startedAt?!1:e.ts>t.endsAt?(this.suppressionWindows.delete(s),!1):e.role==="user"?(this.suppressionWindows.delete(s),!1):!0}}export{R as NativeSessionFusionService};
|
|
@@ -1,22 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { resolveShennianPath } from '../config/index.js';
|
|
5
|
-
const STATE_FILE = resolveShennianPath('native-fusion-state.json');
|
|
6
|
-
export function loadNativeScannerState() {
|
|
7
|
-
try {
|
|
8
|
-
const raw = fs.readFileSync(STATE_FILE, 'utf-8');
|
|
9
|
-
const parsed = JSON.parse(raw);
|
|
10
|
-
if (parsed && typeof parsed === 'object' && parsed.files && typeof parsed.files === 'object') {
|
|
11
|
-
return parsed;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
// ignore missing or malformed state
|
|
16
|
-
}
|
|
17
|
-
return { files: {} };
|
|
18
|
-
}
|
|
19
|
-
export function saveNativeScannerState(state) {
|
|
20
|
-
fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
|
|
21
|
-
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
22
|
-
}
|
|
1
|
+
import r from"node:fs";import i from"node:path";import{resolveShennianPath as o}from"../config/index.js";const n=o("native-fusion-state.json");function f(){try{const t=r.readFileSync(n,"utf-8"),e=JSON.parse(t);if(e&&typeof e=="object"&&e.files&&typeof e.files=="object")return e}catch{}return{files:{}}}function S(t){r.mkdirSync(i.dirname(n),{recursive:!0}),r.writeFileSync(n,JSON.stringify(t,null,2))}export{f as loadNativeScannerState,S as saveNativeScannerState};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
|
package/dist/src/region.js
CHANGED
|
@@ -1,88 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// @test src/__tests__/config.test.ts
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
export const SERVERS = {
|
|
5
|
-
cn: { url: 'https://shennian.net', label: 'China (shennian.net)' },
|
|
6
|
-
// Keep the historical key so old configs and protocol payloads remain
|
|
7
|
-
// compatible. Production is single-domain and resolves both keys to cn.
|
|
8
|
-
global: { url: 'https://shennian.net', label: 'China (shennian.net)' },
|
|
9
|
-
};
|
|
10
|
-
const CN_TIMEZONES = [
|
|
11
|
-
'Asia/Shanghai', 'Asia/Chongqing', 'Asia/Chungking',
|
|
12
|
-
'Asia/Harbin', 'Asia/Urumqi', 'PRC',
|
|
13
|
-
];
|
|
14
|
-
function inferRegionByTimezone() {
|
|
15
|
-
try {
|
|
16
|
-
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
17
|
-
if (CN_TIMEZONES.includes(tz))
|
|
18
|
-
return 'cn';
|
|
19
|
-
}
|
|
20
|
-
catch { /* Intl unavailable */ }
|
|
21
|
-
return 'cn';
|
|
22
|
-
}
|
|
23
|
-
async function detectRegionByIP(timeoutMs = 1500) {
|
|
24
|
-
const controller = new AbortController();
|
|
25
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
26
|
-
try {
|
|
27
|
-
const res = await fetch(`${SERVERS.cn.url}/geo`, { signal: controller.signal });
|
|
28
|
-
const data = await res.json();
|
|
29
|
-
return data.country === 'CN' ? 'cn' : 'cn';
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
finally {
|
|
35
|
-
clearTimeout(timer);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
async function verifyReachable(url, timeoutMs = 3000) {
|
|
39
|
-
const controller = new AbortController();
|
|
40
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
41
|
-
try {
|
|
42
|
-
await fetch(`${url}/health`, { signal: controller.signal });
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
finally {
|
|
49
|
-
clearTimeout(timer);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Detect the best server region and return its URL.
|
|
54
|
-
*
|
|
55
|
-
* Strategy: timezone as primary signal, IP geolocation as supplement.
|
|
56
|
-
* - Chinese timezone → cn immediately (covers servers with UTC too via IP fallback).
|
|
57
|
-
* - Non-Chinese timezone → try IP to detect if physically in China.
|
|
58
|
-
*/
|
|
59
|
-
export async function detectAndChooseServer() {
|
|
60
|
-
console.log(chalk.gray(' Detecting best server...'));
|
|
61
|
-
const tzRegion = inferRegionByTimezone();
|
|
62
|
-
if (tzRegion === 'cn') {
|
|
63
|
-
const reachable = await verifyReachable(SERVERS.cn.url);
|
|
64
|
-
if (reachable) {
|
|
65
|
-
console.log(chalk.gray(` Using ${SERVERS.cn.label} (timezone: China)`));
|
|
66
|
-
return SERVERS.cn.url;
|
|
67
|
-
}
|
|
68
|
-
console.error(chalk.red(' Error: cannot reach any server.'));
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
// Non-CN timezone: check IP to catch servers in China with UTC timezone
|
|
72
|
-
const ipRegion = await detectRegionByIP();
|
|
73
|
-
const region = ipRegion ?? tzRegion;
|
|
74
|
-
const reachable = await verifyReachable(SERVERS[region].url);
|
|
75
|
-
if (reachable) {
|
|
76
|
-
const source = ipRegion ? 'IP geolocation' : 'timezone';
|
|
77
|
-
console.log(chalk.gray(` Using ${SERVERS[region].label} (${source})`));
|
|
78
|
-
return SERVERS[region].url;
|
|
79
|
-
}
|
|
80
|
-
console.error(chalk.red(' Error: cannot reach any server.'));
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
export function urlToRegion(url) {
|
|
84
|
-
return url.includes('shennian.net') ? 'cn' : 'global';
|
|
85
|
-
}
|
|
86
|
-
export function regionToUrl(region) {
|
|
87
|
-
return SERVERS[region].url;
|
|
88
|
-
}
|
|
1
|
+
import o from"chalk";const r={cn:{url:"https://shennian.net",label:"China (shennian.net)"},global:{url:"https://shennian.net",label:"China (shennian.net)"}},l=["Asia/Shanghai","Asia/Chongqing","Asia/Chungking","Asia/Harbin","Asia/Urumqi","PRC"];function s(){try{const n=Intl.DateTimeFormat().resolvedOptions().timeZone;if(l.includes(n))return"cn"}catch{}return"cn"}async function u(n=1500){const t=new AbortController,e=setTimeout(()=>t.abort(),n);try{return(await(await fetch(`${r.cn.url}/geo`,{signal:t.signal})).json()).country==="CN","cn"}catch{return null}finally{clearTimeout(e)}}async function i(n,t=3e3){const e=new AbortController,a=setTimeout(()=>e.abort(),t);try{return await fetch(`${n}/health`,{signal:e.signal}),!0}catch{return!1}finally{clearTimeout(a)}}async function g(){console.log(o.gray(" Detecting best server..."));const n=s();if(n==="cn"){if(await i(r.cn.url))return console.log(o.gray(` Using ${r.cn.label} (timezone: China)`)),r.cn.url;console.error(o.red(" Error: cannot reach any server.")),process.exit(1)}const t=await u(),e=t??n;if(await i(r[e].url)){const c=t?"IP geolocation":"timezone";return console.log(o.gray(` Using ${r[e].label} (${c})`)),r[e].url}console.error(o.red(" Error: cannot reach any server.")),process.exit(1)}function f(n){return n.includes("shennian.net")?"cn":"global"}function b(n){return r[n].url}export{r as SERVERS,g as detectAndChooseServer,b as regionToUrl,f as urlToRegion};
|