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.
- package/README.md +3 -2
- package/dist/bin/shennian.js +1 -1
- package/dist/publish-build-manifest.json +32 -27
- package/dist/src/agents/adapter.js +1 -19
- package/dist/src/agents/claude-managed-permissions.js +1 -248
- package/dist/src/agents/claude.js +8 -393
- package/dist/src/agents/codex-managed-permissions.js +1 -294
- package/dist/src/agents/codex-managed-room-tools.js +1 -249
- package/dist/src/agents/codex-utils.js +7 -200
- package/dist/src/agents/codex.d.ts +7 -1
- package/dist/src/agents/codex.js +17 -1352
- package/dist/src/agents/config-status.js +1 -226
- package/dist/src/agents/cursor-managed-permissions.js +2 -299
- package/dist/src/agents/cursor.js +1 -314
- package/dist/src/agents/custom.js +4 -418
- package/dist/src/agents/external-channel-instructions.js +11 -94
- package/dist/src/agents/gemini.js +1 -180
- package/dist/src/agents/manager.js +18 -250
- package/dist/src/agents/model-registry/cache.js +1 -37
- package/dist/src/agents/model-registry/discovery.js +2 -188
- package/dist/src/agents/model-registry/parsers.js +4 -521
- package/dist/src/agents/model-registry/runner.js +1 -30
- package/dist/src/agents/model-registry/service.js +1 -115
- package/dist/src/agents/model-registry/types.js +1 -8
- package/dist/src/agents/model-registry.js +1 -19
- package/dist/src/agents/native-capabilities.js +1 -237
- package/dist/src/agents/openclaw.js +2 -293
- package/dist/src/agents/opencode-managed-permissions.js +2 -187
- package/dist/src/agents/opencode.js +1 -298
- package/dist/src/agents/platform-instructions.js +17 -46
- package/dist/src/agents/runtime-compatibility.js +1 -44
- package/dist/src/agents/workbuddy-managed-permissions.js +2 -329
- package/dist/src/agents/workbuddy.js +3 -444
- package/dist/src/commands/logout.d.ts +3 -0
- package/dist/src/commands/logout.js +1 -0
- package/dist/src/daemon-ipc/paths.js +1 -1
- package/dist/src/fs/boundary.js +1 -185
- package/dist/src/fs/handler.js +1 -130
- package/dist/src/fs/security.js +1 -32
- package/dist/src/fs/text-decoder.js +1 -110
- package/dist/src/index.js +3 -587
- package/dist/src/manager/prompt.js +29 -34
- package/dist/src/manager/registry.js +1 -317
- package/dist/src/manager/retired-external-channel-compat.js +1 -40
- package/dist/src/manager/runtime.js +23 -1514
- package/dist/src/native-fusion/codex-parser.d.ts +3 -1
- package/dist/src/native-fusion/codex-parser.js +4 -660
- package/dist/src/native-fusion/codex-user-input.js +5 -102
- package/dist/src/native-fusion/config.js +1 -5
- package/dist/src/native-fusion/file-watcher.js +1 -191
- package/dist/src/native-fusion/local-repository.js +2 -569
- package/dist/src/native-fusion/opencode-parser.js +3 -130
- package/dist/src/native-fusion/parser-common.d.ts +6 -1
- package/dist/src/native-fusion/parser-common.js +4 -4
- package/dist/src/native-fusion/parsers.d.ts +4 -2
- package/dist/src/native-fusion/parsers.js +3 -140
- package/dist/src/native-fusion/service.js +2 -739
- package/dist/src/native-fusion/session-header.js +2 -101
- package/dist/src/native-fusion/state.js +2 -104
- package/dist/src/native-fusion/types.js +1 -1
- package/dist/src/personal-sync/machine-authority.js +1 -34
- package/dist/src/room/activation-audit.js +2 -45
- package/dist/src/room/activation-policy.js +1 -30
- package/dist/src/room/activation-service.js +8 -408
- package/dist/src/room/invite-bootstrap.js +1 -156
- package/dist/src/room/managed-prompt-composer.d.ts +1 -1
- package/dist/src/room/managed-prompt-composer.js +17 -16
- package/dist/src/room/managed-room-binding.js +1 -167
- package/dist/src/room/managed-room-ipc.d.ts +4 -0
- package/dist/src/room/managed-room-ipc.js +2 -2
- package/dist/src/room/mirror-sync-service.d.ts +1 -0
- package/dist/src/room/mirror-sync-service.js +1 -183
- package/dist/src/room/room-binding-client.js +1 -151
- package/dist/src/session/archive-zip.js +1 -220
- package/dist/src/session/deletion-sync-state.js +2 -136
- package/dist/src/session/handlers/agent-capabilities.js +1 -14
- 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 -1030
- package/dist/src/session/handlers/control.js +1 -61
- package/dist/src/session/handlers/fs.js +1 -594
- package/dist/src/session/handlers/message-status.js +1 -15
- package/dist/src/session/handlers/remote-access.js +1 -43
- package/dist/src/session/handlers/room-managed.js +1 -101
- package/dist/src/session/handlers/session-refresh.js +1 -35
- package/dist/src/session/handlers/title.js +1 -79
- package/dist/src/session/handlers/tool-detail.js +1 -149
- package/dist/src/session/handlers/voice-window.js +1 -62
- package/dist/src/session/history-subscriptions.js +1 -470
- package/dist/src/session/manager.d.ts +1 -0
- package/dist/src/session/manager.js +1 -658
- package/dist/src/session/native-cleanup-outbox.js +2 -287
- package/dist/src/session/native-session-lifecycle.js +6 -327
- package/dist/src/session/queue.d.ts +8 -0
- package/dist/src/session/queue.js +6 -6
- package/dist/src/upgrade/scheduler.js +1 -126
- package/node_modules/@shennian/wire/dist/agent-workspace.js +2 -2
- package/package.json +1 -1
|
@@ -1,15 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// @test src/__tests__/session-store.test.ts
|
|
3
|
-
import { getMessageDeliveryStatus } from '../store.js';
|
|
4
|
-
import { assertPersonalSyncIdentity } from '../../personal-sync/epoch.js';
|
|
5
|
-
export function handleSessionMessageStatus(runtime, req) {
|
|
6
|
-
const params = req.params;
|
|
7
|
-
assertPersonalSyncIdentity(params);
|
|
8
|
-
if (!params.sessionId || !params.clientMessageId) {
|
|
9
|
-
throw new Error('sessionId and clientMessageId are required');
|
|
10
|
-
}
|
|
11
|
-
runtime.client.sendRes({
|
|
12
|
-
type: 'res', id: req.id, ok: true,
|
|
13
|
-
payload: getMessageDeliveryStatus(params.sessionId, params.clientMessageId),
|
|
14
|
-
});
|
|
15
|
-
}
|
|
1
|
+
import{getMessageDeliveryStatus as a}from"../store.js";import{assertPersonalSyncIdentity as n}from"../../personal-sync/epoch.js";function o(t,s){const e=s.params;if(n(e),!e.sessionId||!e.clientMessageId)throw new Error("sessionId and clientMessageId are required");t.client.sendRes({type:"res",id:s.id,ok:!0,payload:a(e.sessionId,e.clientMessageId)})}export{o as handleSessionMessageStatus};
|
|
@@ -1,43 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// @test src/__tests__/remote-access-pause.test.ts
|
|
3
|
-
export async function handleRemoteAccessPause(client, controller, req) {
|
|
4
|
-
if (!controller) {
|
|
5
|
-
await client.sendResAndWait({
|
|
6
|
-
type: 'res',
|
|
7
|
-
id: req.id,
|
|
8
|
-
ok: false,
|
|
9
|
-
error: 'upgrade_required',
|
|
10
|
-
}).catch(() => { });
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
try {
|
|
14
|
-
const preparation = controller.prepare();
|
|
15
|
-
if (!preparation.autoStartDisabled) {
|
|
16
|
-
throw new Error(preparation.autoStartError
|
|
17
|
-
? `remote_access_autostart_disable_failed: ${preparation.autoStartError}`
|
|
18
|
-
: 'remote_access_autostart_disable_failed');
|
|
19
|
-
}
|
|
20
|
-
const acknowledgement = await client.sendBufferedEvent({
|
|
21
|
-
type: 'event',
|
|
22
|
-
id: `remote-access-paused-${req.id}`,
|
|
23
|
-
event: 'machine.remote-access.paused',
|
|
24
|
-
payload: { disabledAt: preparation.disabledAt },
|
|
25
|
-
}, 30_000);
|
|
26
|
-
const payload = acknowledgement && typeof acknowledgement.payload === 'object'
|
|
27
|
-
? acknowledgement.payload
|
|
28
|
-
: { remoteAccessDisabledAt: preparation.disabledAt };
|
|
29
|
-
await client.sendResAndWait({ type: 'res', id: req.id, ok: true, payload });
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
await client.sendResAndWait({
|
|
33
|
-
type: 'res',
|
|
34
|
-
id: req.id,
|
|
35
|
-
ok: false,
|
|
36
|
-
error: error instanceof Error ? error.message : String(error),
|
|
37
|
-
}).catch(() => { });
|
|
38
|
-
}
|
|
39
|
-
finally {
|
|
40
|
-
if (controller.isDisabled())
|
|
41
|
-
controller.shutdownAfterResponse();
|
|
42
|
-
}
|
|
43
|
-
}
|
|
1
|
+
async function i(a,t,s){if(!t){await a.sendResAndWait({type:"res",id:s.id,ok:!1,error:"upgrade_required"}).catch(()=>{});return}try{const e=t.prepare();if(!e.autoStartDisabled)throw new Error(e.autoStartError?`remote_access_autostart_disable_failed: ${e.autoStartError}`:"remote_access_autostart_disable_failed");const r=await a.sendBufferedEvent({type:"event",id:`remote-access-paused-${s.id}`,event:"machine.remote-access.paused",payload:{disabledAt:e.disabledAt}},3e4),d=r&&typeof r.payload=="object"?r.payload:{remoteAccessDisabledAt:e.disabledAt};await a.sendResAndWait({type:"res",id:s.id,ok:!0,payload:d})}catch(e){await a.sendResAndWait({type:"res",id:s.id,ok:!1,error:e instanceof Error?e.message:String(e)}).catch(()=>{})}finally{t.isDisabled()&&t.shutdownAfterResponse()}}export{i as handleRemoteAccessPause};
|
|
@@ -1,101 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// @test src/__tests__/managed-room-binding.test.ts
|
|
3
|
-
import { isAvailableAgentType } from '@shennian/wire';
|
|
4
|
-
const SAFE_ID = /^[A-Za-z0-9_.:@/-]{1,191}$/;
|
|
5
|
-
const BIND_PARAMS = new Set([
|
|
6
|
-
'roomId',
|
|
7
|
-
'agentIdentityId',
|
|
8
|
-
'nianSessionId',
|
|
9
|
-
'assignmentIntentId',
|
|
10
|
-
'agentType',
|
|
11
|
-
]);
|
|
12
|
-
const RESTART_PARAMS = new Set([
|
|
13
|
-
'roomId',
|
|
14
|
-
'bindingId',
|
|
15
|
-
'agentIdentityId',
|
|
16
|
-
'previousNianSessionId',
|
|
17
|
-
'nianSessionId',
|
|
18
|
-
'assignmentIntentId',
|
|
19
|
-
'agentType',
|
|
20
|
-
]);
|
|
21
|
-
export async function handleManagedRoomBind(runtime, binder, req) {
|
|
22
|
-
if (!binder)
|
|
23
|
-
throw new Error('Managed Room binding is unavailable.');
|
|
24
|
-
if (Object.keys(req.params).some((key) => !BIND_PARAMS.has(key))) {
|
|
25
|
-
throw new Error('Managed Room binding contains unsupported parameters.');
|
|
26
|
-
}
|
|
27
|
-
const roomId = requireId(req.params.roomId, 'roomId');
|
|
28
|
-
const agentIdentityId = requireId(req.params.agentIdentityId, 'agentIdentityId');
|
|
29
|
-
const nianSessionId = requireId(req.params.nianSessionId, 'nianSessionId');
|
|
30
|
-
const assignmentIntentId = requireId(req.params.assignmentIntentId, 'assignmentIntentId');
|
|
31
|
-
const agentType = requireManagedAgentType(req.params.agentType);
|
|
32
|
-
await discardPreBindingSession(runtime, nianSessionId);
|
|
33
|
-
const result = await binder.bind({
|
|
34
|
-
roomId,
|
|
35
|
-
agentIdentityId,
|
|
36
|
-
nianSessionId,
|
|
37
|
-
assignmentIntentId,
|
|
38
|
-
sourceSessionKey: managedSourceSessionKey(nianSessionId),
|
|
39
|
-
agentType,
|
|
40
|
-
});
|
|
41
|
-
runtime.client.sendRes({ type: 'res', id: req.id, ok: true, payload: result });
|
|
42
|
-
}
|
|
43
|
-
export async function handleManagedRoomRestart(runtime, binder, req) {
|
|
44
|
-
if (!binder)
|
|
45
|
-
throw new Error('Managed Room binding is unavailable.');
|
|
46
|
-
if (Object.keys(req.params).some((key) => !RESTART_PARAMS.has(key))) {
|
|
47
|
-
throw new Error('Managed Room restart contains unsupported parameters.');
|
|
48
|
-
}
|
|
49
|
-
const roomId = requireId(req.params.roomId, 'roomId');
|
|
50
|
-
const bindingId = requireId(req.params.bindingId, 'bindingId');
|
|
51
|
-
const agentIdentityId = requireId(req.params.agentIdentityId, 'agentIdentityId');
|
|
52
|
-
const previousNianSessionId = requireId(req.params.previousNianSessionId, 'previousNianSessionId');
|
|
53
|
-
const nianSessionId = requireId(req.params.nianSessionId, 'nianSessionId');
|
|
54
|
-
const assignmentIntentId = requireId(req.params.assignmentIntentId, 'assignmentIntentId');
|
|
55
|
-
const agentType = requireManagedAgentType(req.params.agentType);
|
|
56
|
-
await discardPreBindingSession(runtime, nianSessionId);
|
|
57
|
-
const result = await binder.restart({
|
|
58
|
-
roomId,
|
|
59
|
-
bindingId,
|
|
60
|
-
agentIdentityId,
|
|
61
|
-
previousNianSessionId,
|
|
62
|
-
nianSessionId,
|
|
63
|
-
assignmentIntentId,
|
|
64
|
-
sourceSessionKey: managedSourceSessionKey(nianSessionId),
|
|
65
|
-
agentType,
|
|
66
|
-
});
|
|
67
|
-
runtime.client.sendRes({ type: 'res', id: req.id, ok: true, payload: result });
|
|
68
|
-
}
|
|
69
|
-
function requireId(value, field) {
|
|
70
|
-
if (typeof value !== 'string' || !SAFE_ID.test(value)) {
|
|
71
|
-
throw new Error(`Invalid ${field}.`);
|
|
72
|
-
}
|
|
73
|
-
return value;
|
|
74
|
-
}
|
|
75
|
-
function requireManagedAgentType(value) {
|
|
76
|
-
if (typeof value !== 'string' ||
|
|
77
|
-
!isAvailableAgentType(value) ||
|
|
78
|
-
value === 'manager' ||
|
|
79
|
-
value === 'pi' ||
|
|
80
|
-
value === 'openclaw') {
|
|
81
|
-
throw new Error('managed_agent_runtime_unsupported');
|
|
82
|
-
}
|
|
83
|
-
return value;
|
|
84
|
-
}
|
|
85
|
-
function managedSourceSessionKey(nianSessionId) {
|
|
86
|
-
return `managed:${nianSessionId}`;
|
|
87
|
-
}
|
|
88
|
-
async function discardPreBindingSession(runtime, nianSessionId) {
|
|
89
|
-
const session = runtime.sessions.get(nianSessionId);
|
|
90
|
-
if (!session)
|
|
91
|
-
return;
|
|
92
|
-
runtime.sessions.delete(nianSessionId);
|
|
93
|
-
if (session.managedRoomContextToken) {
|
|
94
|
-
runtime.revokeManagedRoomContextToken?.(session.managedRoomContextToken);
|
|
95
|
-
session.managedRoomContextToken = null;
|
|
96
|
-
}
|
|
97
|
-
if (session.heartbeatTimer)
|
|
98
|
-
clearInterval(session.heartbeatTimer);
|
|
99
|
-
session.adapter.removeAllListeners();
|
|
100
|
-
await session.adapter.stop();
|
|
101
|
-
}
|
|
1
|
+
import{isAvailableAgentType as S}from"@shennian/wire";const w=/^[A-Za-z0-9_.:@/-]{1,191}$/,f=new Set(["roomId","agentIdentityId","nianSessionId","assignmentIntentId","agentType"]),R=new Set(["roomId","bindingId","agentIdentityId","previousNianSessionId","nianSessionId","assignmentIntentId","agentType"]);async function T(e,t,n){if(!t)throw new Error("Managed Room binding is unavailable.");if(Object.keys(n.params).some(m=>!f.has(m)))throw new Error("Managed Room binding contains unsupported parameters.");const i=a(n.params.roomId,"roomId"),d=a(n.params.agentIdentityId,"agentIdentityId"),s=a(n.params.nianSessionId,"nianSessionId"),r=a(n.params.assignmentIntentId,"assignmentIntentId"),o=g(n.params.agentType);await c(e,s);const I=await t.bind({roomId:i,agentIdentityId:d,nianSessionId:s,assignmentIntentId:r,sourceSessionKey:p(s),agentType:o});e.client.sendRes({type:"res",id:n.id,ok:!0,payload:I})}async function b(e,t,n){if(!t)throw new Error("Managed Room binding is unavailable.");if(Object.keys(n.params).some(u=>!R.has(u)))throw new Error("Managed Room restart contains unsupported parameters.");const i=a(n.params.roomId,"roomId"),d=a(n.params.bindingId,"bindingId"),s=a(n.params.agentIdentityId,"agentIdentityId"),r=a(n.params.previousNianSessionId,"previousNianSessionId"),o=a(n.params.nianSessionId,"nianSessionId"),I=a(n.params.assignmentIntentId,"assignmentIntentId"),m=g(n.params.agentType);await c(e,o);const y=await t.restart({roomId:i,bindingId:d,agentIdentityId:s,previousNianSessionId:r,nianSessionId:o,assignmentIntentId:I,sourceSessionKey:p(o),agentType:m});e.client.sendRes({type:"res",id:n.id,ok:!0,payload:y})}function a(e,t){if(typeof e!="string"||!w.test(e))throw new Error(`Invalid ${t}.`);return e}function g(e){if(typeof e!="string"||!S(e)||e==="manager"||e==="pi"||e==="openclaw")throw new Error("managed_agent_runtime_unsupported");return e}function p(e){return`managed:${e}`}async function c(e,t){const n=e.sessions.get(t);n&&(e.sessions.delete(t),n.managedRoomContextToken&&(e.revokeManagedRoomContextToken?.(n.managedRoomContextToken),n.managedRoomContextToken=null),n.heartbeatTimer&&clearInterval(n.heartbeatTimer),n.adapter.removeAllListeners(),await n.adapter.stop())}export{T as handleManagedRoomBind,b as handleManagedRoomRestart};
|
|
@@ -1,35 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// @test src/__tests__/session-manager.test.ts
|
|
3
|
-
export async function handleSessionRefresh(runtime, req) {
|
|
4
|
-
const params = req.params;
|
|
5
|
-
if (!params.sessionId) {
|
|
6
|
-
runtime.client.sendRes({
|
|
7
|
-
type: 'res',
|
|
8
|
-
id: req.id,
|
|
9
|
-
ok: false,
|
|
10
|
-
error: 'sessionId is required',
|
|
11
|
-
});
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
if (!runtime.nativeFusion) {
|
|
15
|
-
runtime.client.sendRes({
|
|
16
|
-
type: 'res',
|
|
17
|
-
id: req.id,
|
|
18
|
-
ok: true,
|
|
19
|
-
payload: { scanned: false },
|
|
20
|
-
});
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
await runtime.nativeFusion.scanNow();
|
|
24
|
-
runtime.client.sendRes({
|
|
25
|
-
type: 'res',
|
|
26
|
-
id: req.id,
|
|
27
|
-
ok: true,
|
|
28
|
-
payload: {
|
|
29
|
-
scanned: true,
|
|
30
|
-
sessionId: params.sessionId,
|
|
31
|
-
agentType: params.agentType ?? null,
|
|
32
|
-
agentSessionId: params.agentSessionId ?? null,
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
}
|
|
1
|
+
async function a(s,e){const n=e.params;if(!n.sessionId){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId is required"});return}if(!s.nativeFusion){s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{scanned:!1}});return}await s.nativeFusion.scanNow(),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{scanned:!0,sessionId:n.sessionId,agentType:n.agentType??null,agentSessionId:n.agentSessionId??null}})}export{a as handleSessionRefresh};
|
|
@@ -1,79 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// @test src/__tests__/session-manager.test.ts
|
|
3
|
-
import { createAgent } from '../../agents/adapter.js';
|
|
4
|
-
import { buildManagedAgentEnv } from '../../agents/config-status.js';
|
|
5
|
-
import { recordSession } from '../store.js';
|
|
6
|
-
import { requestSessionIndexPublish } from '../index-publisher.js';
|
|
7
|
-
import { assertPersonalSyncIdentity } from '../../personal-sync/epoch.js';
|
|
8
|
-
function cleanString(value) {
|
|
9
|
-
return typeof value === 'string' ? value.trim() : '';
|
|
10
|
-
}
|
|
11
|
-
export async function handleSessionTitleSet(runtime, req) {
|
|
12
|
-
const params = req.params;
|
|
13
|
-
assertPersonalSyncIdentity(params);
|
|
14
|
-
const sessionId = cleanString(params.sessionId);
|
|
15
|
-
const title = cleanString(params.title);
|
|
16
|
-
const agentSessionId = cleanString(params.agentSessionId);
|
|
17
|
-
const agentType = cleanString(params.agentType);
|
|
18
|
-
const workDir = cleanString(params.workDir) || process.cwd();
|
|
19
|
-
const titleOriginEventId = cleanString(params.titleOriginEventId);
|
|
20
|
-
const titleRevision = params.titleRevision;
|
|
21
|
-
if (!sessionId || !title || !agentType || params.titleSource !== 'shennian_manual'
|
|
22
|
-
|| !Number.isSafeInteger(titleRevision) || Number(titleRevision) < 1 || !titleOriginEventId) {
|
|
23
|
-
runtime.client.sendRes({
|
|
24
|
-
type: 'res',
|
|
25
|
-
id: req.id,
|
|
26
|
-
ok: false,
|
|
27
|
-
error: 'explicit shennian_manual title authority is required',
|
|
28
|
-
});
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
recordSession({
|
|
32
|
-
sessionId,
|
|
33
|
-
agentType,
|
|
34
|
-
workDir,
|
|
35
|
-
agentSessionId: agentSessionId || null,
|
|
36
|
-
title,
|
|
37
|
-
titleSource: 'shennian_manual',
|
|
38
|
-
titleRevision: Number(titleRevision),
|
|
39
|
-
titleLockedByUser: true,
|
|
40
|
-
titleOriginEventId,
|
|
41
|
-
});
|
|
42
|
-
requestSessionIndexPublish(runtime.client);
|
|
43
|
-
if (agentType !== 'codex' || !agentSessionId) {
|
|
44
|
-
runtime.client.sendRes({
|
|
45
|
-
type: 'res',
|
|
46
|
-
id: req.id,
|
|
47
|
-
ok: true,
|
|
48
|
-
payload: { localApplied: true, nativeWriteback: 'unsupported' },
|
|
49
|
-
});
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const active = runtime.sessions.get(sessionId);
|
|
53
|
-
if (active?.agentType === 'codex' && active.adapter.setTitle) {
|
|
54
|
-
await active.adapter.setTitle(agentSessionId, title, active.workDir || workDir);
|
|
55
|
-
active.agentSessionId = agentSessionId;
|
|
56
|
-
active.lastActiveAt = Date.now();
|
|
57
|
-
runtime.client.sendRes({ type: 'res', id: req.id, ok: true, payload: { synced: true } });
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
const adapter = createAgent('codex');
|
|
61
|
-
if (!adapter?.setTitle) {
|
|
62
|
-
runtime.client.sendRes({
|
|
63
|
-
type: 'res',
|
|
64
|
-
id: req.id,
|
|
65
|
-
ok: false,
|
|
66
|
-
error: 'Codex adapter does not support title sync',
|
|
67
|
-
});
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
adapter.configure?.({ sessionId, env: buildManagedAgentEnv('codex') });
|
|
71
|
-
try {
|
|
72
|
-
await adapter.start(sessionId, workDir, agentSessionId);
|
|
73
|
-
await adapter.setTitle(agentSessionId, title, workDir);
|
|
74
|
-
runtime.client.sendRes({ type: 'res', id: req.id, ok: true, payload: { synced: true } });
|
|
75
|
-
}
|
|
76
|
-
finally {
|
|
77
|
-
await adapter.stop().catch(() => { });
|
|
78
|
-
}
|
|
79
|
-
}
|
|
1
|
+
import{createAgent as y}from"../../agents/adapter.js";import{buildManagedAgentEnv as f}from"../../agents/config-status.js";import{recordSession as g}from"../store.js";import{requestSessionIndexPublish as m}from"../index-publisher.js";import{assertPersonalSyncIdentity as S}from"../../personal-sync/epoch.js";function o(e){return typeof e=="string"?e.trim():""}async function R(e,i){const t=i.params;S(t);const r=o(t.sessionId),d=o(t.title),n=o(t.agentSessionId),c=o(t.agentType),l=o(t.workDir)||process.cwd(),u=o(t.titleOriginEventId),p=t.titleRevision;if(!r||!d||!c||t.titleSource!=="shennian_manual"||!Number.isSafeInteger(p)||Number(p)<1||!u){e.client.sendRes({type:"res",id:i.id,ok:!1,error:"explicit shennian_manual title authority is required"});return}if(g({sessionId:r,agentType:c,workDir:l,agentSessionId:n||null,title:d,titleSource:"shennian_manual",titleRevision:Number(p),titleLockedByUser:!0,titleOriginEventId:u}),m(e.client),c!=="codex"||!n){e.client.sendRes({type:"res",id:i.id,ok:!0,payload:{localApplied:!0,nativeWriteback:"unsupported"}});return}const s=e.sessions.get(r);if(s?.agentType==="codex"&&s.adapter.setTitle){await s.adapter.setTitle(n,d,s.workDir||l),s.agentSessionId=n,s.lastActiveAt=Date.now(),e.client.sendRes({type:"res",id:i.id,ok:!0,payload:{synced:!0}});return}const a=y("codex");if(!a?.setTitle){e.client.sendRes({type:"res",id:i.id,ok:!1,error:"Codex adapter does not support title sync"});return}a.configure?.({sessionId:r,env:f("codex")});try{await a.start(r,l,n),await a.setTitle(n,d,l),e.client.sendRes({type:"res",id:i.id,ok:!0,payload:{synced:!0}})}finally{await a.stop().catch(()=>{})}}export{R as handleSessionTitleSet};
|
|
@@ -1,149 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// @test src/__tests__/session-manager.test.ts
|
|
3
|
-
import { isSystemControlPayload, SESSION_TOOL_DETAIL_LIMITS, } from '@shennian/wire';
|
|
4
|
-
import { readToolDetail } from '../tool-detail-store.js';
|
|
5
|
-
import { assertPersonalSyncIdentity, getPersonalSyncIdentity } from '../../personal-sync/epoch.js';
|
|
6
|
-
import { getDaemonInstallationId } from '../store.js';
|
|
7
|
-
const SENSITIVE_KEY_RE = /(?:token|api[-_]?key|password|passwd|pwd|secret|authorization|cookie|credential|private[-_]?key)/i;
|
|
8
|
-
const SENSITIVE_ASSIGNMENT_RE = /\b([A-Z0-9_]*(?:TOKEN|KEY|SECRET|PASSWORD|PASSWD|PWD|AUTH)[A-Z0-9_]*)=("[^"]*"|'[^']*'|[^\s]+)/gi;
|
|
9
|
-
const BEARER_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi;
|
|
10
|
-
const OPENAI_KEY_RE = /\bsk-[A-Za-z0-9_-]{12,}\b/g;
|
|
11
|
-
function asRecord(value) {
|
|
12
|
-
return value && typeof value === 'object' && !Array.isArray(value)
|
|
13
|
-
? value
|
|
14
|
-
: null;
|
|
15
|
-
}
|
|
16
|
-
function clampMaxBytes(value) {
|
|
17
|
-
const parsed = typeof value === 'number' ? value : Number(value);
|
|
18
|
-
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
19
|
-
return SESSION_TOOL_DETAIL_LIMITS.defaultMaxBytes;
|
|
20
|
-
return Math.min(Math.max(SESSION_TOOL_DETAIL_LIMITS.minMaxBytes, Math.floor(parsed)), SESSION_TOOL_DETAIL_LIMITS.maxMaxBytes);
|
|
21
|
-
}
|
|
22
|
-
function redactString(value, maxChars) {
|
|
23
|
-
if (isSystemControlPayload(value))
|
|
24
|
-
return '[SYSTEM CONTEXT REDACTED]';
|
|
25
|
-
const redacted = value
|
|
26
|
-
.replace(SENSITIVE_ASSIGNMENT_RE, '$1=[REDACTED]')
|
|
27
|
-
.replace(BEARER_RE, 'Bearer [REDACTED]')
|
|
28
|
-
.replace(OPENAI_KEY_RE, 'sk-[REDACTED]');
|
|
29
|
-
return redacted.length > maxChars ? `${redacted.slice(0, maxChars)}…` : redacted;
|
|
30
|
-
}
|
|
31
|
-
function sanitizeValue(value, maxChars, depth = 0) {
|
|
32
|
-
if (value == null)
|
|
33
|
-
return value;
|
|
34
|
-
if (typeof value === 'string')
|
|
35
|
-
return redactString(value, maxChars);
|
|
36
|
-
if (typeof value === 'number' || typeof value === 'boolean')
|
|
37
|
-
return value;
|
|
38
|
-
if (depth >= 4)
|
|
39
|
-
return '[Truncated]';
|
|
40
|
-
if (Array.isArray(value))
|
|
41
|
-
return value.slice(0, 20).map((item) => sanitizeValue(item, maxChars, depth + 1));
|
|
42
|
-
if (typeof value === 'object') {
|
|
43
|
-
const output = {};
|
|
44
|
-
for (const [key, raw] of Object.entries(value).slice(0, 50)) {
|
|
45
|
-
output[key] = SENSITIVE_KEY_RE.test(key) ? '[REDACTED]' : sanitizeValue(raw, maxChars, depth + 1);
|
|
46
|
-
}
|
|
47
|
-
return output;
|
|
48
|
-
}
|
|
49
|
-
return String(value);
|
|
50
|
-
}
|
|
51
|
-
function extractCommand(args, maxChars) {
|
|
52
|
-
const raw = asRecord(args)?.command;
|
|
53
|
-
if (typeof raw === 'string')
|
|
54
|
-
return redactString(raw, maxChars);
|
|
55
|
-
if (Array.isArray(raw))
|
|
56
|
-
return redactString(raw.map(String).join(' '), maxChars);
|
|
57
|
-
return undefined;
|
|
58
|
-
}
|
|
59
|
-
function byteSize(value) {
|
|
60
|
-
return Buffer.byteLength(JSON.stringify(value), 'utf8');
|
|
61
|
-
}
|
|
62
|
-
function finalizeByteSize(payload) {
|
|
63
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
64
|
-
const next = byteSize(payload);
|
|
65
|
-
if (next === payload.byteSize)
|
|
66
|
-
break;
|
|
67
|
-
payload.byteSize = next;
|
|
68
|
-
}
|
|
69
|
-
return payload;
|
|
70
|
-
}
|
|
71
|
-
function unavailable(request, reason) {
|
|
72
|
-
const payload = {
|
|
73
|
-
...getPersonalSyncIdentity(getDaemonInstallationId()),
|
|
74
|
-
sessionId: request.sessionId,
|
|
75
|
-
toolCallId: request.toolCallId,
|
|
76
|
-
detailRevision: request.detailRevision,
|
|
77
|
-
detailStatus: reason === 'not_found' ? 'not_found' : 'unavailable',
|
|
78
|
-
resultOmitted: true,
|
|
79
|
-
redacted: true,
|
|
80
|
-
byteSize: 0,
|
|
81
|
-
expiresAt: new Date(Date.now() + SESSION_TOOL_DETAIL_LIMITS.responseTtlMs).toISOString(),
|
|
82
|
-
source: 'unavailable',
|
|
83
|
-
reason,
|
|
84
|
-
};
|
|
85
|
-
return finalizeByteSize(payload);
|
|
86
|
-
}
|
|
87
|
-
export async function handleSessionToolDetail(runtime, req) {
|
|
88
|
-
const params = req.params;
|
|
89
|
-
assertPersonalSyncIdentity(params);
|
|
90
|
-
const sessionId = typeof params.sessionId === 'string' ? params.sessionId : '';
|
|
91
|
-
const toolCallId = typeof params.toolCallId === 'string' ? params.toolCallId : '';
|
|
92
|
-
const detailRevision = typeof params.detailRevision === 'number' && Number.isSafeInteger(params.detailRevision)
|
|
93
|
-
? params.detailRevision
|
|
94
|
-
: -1;
|
|
95
|
-
if (!sessionId || !toolCallId || detailRevision < 0) {
|
|
96
|
-
runtime.client.sendRes({
|
|
97
|
-
type: 'res', id: req.id, ok: false,
|
|
98
|
-
error: 'upgrade_required: sessionId, toolCallId and detailRevision are required',
|
|
99
|
-
});
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
const maxBytes = clampMaxBytes(params.maxBytes);
|
|
103
|
-
const request = {
|
|
104
|
-
...getPersonalSyncIdentity(getDaemonInstallationId()), sessionId, toolCallId, detailRevision, maxBytes,
|
|
105
|
-
};
|
|
106
|
-
const detail = readToolDetail(sessionId, toolCallId);
|
|
107
|
-
if (!detail) {
|
|
108
|
-
runtime.client.sendRes({ type: 'res', id: req.id, ok: true, payload: unavailable(request, 'not_found') });
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
if (detail.detailRevision !== detailRevision) {
|
|
112
|
-
runtime.client.sendRes({
|
|
113
|
-
type: 'res', id: req.id, ok: true, payload: unavailable(request, 'revision_mismatch'),
|
|
114
|
-
});
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
const maxChars = Math.max(512, Math.floor(maxBytes / 4));
|
|
118
|
-
const argsSummary = detail.args == null ? undefined : sanitizeValue(detail.args, maxChars);
|
|
119
|
-
const command = extractCommand(detail.args, maxChars);
|
|
120
|
-
const result = typeof detail.result === 'string'
|
|
121
|
-
? redactString(detail.result, maxChars)
|
|
122
|
-
: detail.result == null
|
|
123
|
-
? undefined
|
|
124
|
-
: redactString(JSON.stringify(sanitizeValue(detail.result, maxChars)), maxChars);
|
|
125
|
-
const expiresAt = new Date(Date.now() + SESSION_TOOL_DETAIL_LIMITS.responseTtlMs).toISOString();
|
|
126
|
-
let payload = {
|
|
127
|
-
...getPersonalSyncIdentity(getDaemonInstallationId()),
|
|
128
|
-
sessionId, toolCallId, detailRevision,
|
|
129
|
-
detailStatus: 'available',
|
|
130
|
-
status: detail.status,
|
|
131
|
-
...(command ? { command } : {}),
|
|
132
|
-
...(argsSummary !== undefined ? { argsSummary } : {}),
|
|
133
|
-
...(result !== undefined ? { result } : {}),
|
|
134
|
-
resultOmitted: result === undefined,
|
|
135
|
-
redacted: true,
|
|
136
|
-
byteSize: 0,
|
|
137
|
-
expiresAt,
|
|
138
|
-
source: 'local-tool-index',
|
|
139
|
-
};
|
|
140
|
-
finalizeByteSize(payload);
|
|
141
|
-
if (payload.byteSize > maxBytes) {
|
|
142
|
-
delete payload.result;
|
|
143
|
-
payload.resultOmitted = true;
|
|
144
|
-
finalizeByteSize(payload);
|
|
145
|
-
}
|
|
146
|
-
if (payload.byteSize > maxBytes)
|
|
147
|
-
payload = unavailable(request, 'response_too_large');
|
|
148
|
-
runtime.client.sendRes({ type: 'res', id: req.id, ok: true, payload });
|
|
149
|
-
}
|
|
1
|
+
import{isSystemControlPayload as A,SESSION_TOOL_DETAIL_LIMITS as l}from"@shennian/wire";import{readToolDetail as _}from"../tool-detail-store.js";import{assertPersonalSyncIdentity as T,getPersonalSyncIdentity as p}from"../../personal-sync/epoch.js";import{getDaemonInstallationId as m}from"../store.js";const D=/(?:token|api[-_]?key|password|passwd|pwd|secret|authorization|cookie|credential|private[-_]?key)/i,O=/\b([A-Z0-9_]*(?:TOKEN|KEY|SECRET|PASSWORD|PASSWD|PWD|AUTH)[A-Z0-9_]*)=("[^"]*"|'[^']*'|[^\s]+)/gi,M=/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi,z=/\bsk-[A-Za-z0-9_-]{12,}\b/g;function N(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:null}function k(e){const n=typeof e=="number"?e:Number(e);return!Number.isFinite(n)||n<=0?l.defaultMaxBytes:Math.min(Math.max(l.minMaxBytes,Math.floor(n)),l.maxMaxBytes)}function c(e,n){if(A(e))return"[SYSTEM CONTEXT REDACTED]";const t=e.replace(O,"$1=[REDACTED]").replace(M,"Bearer [REDACTED]").replace(z,"sk-[REDACTED]");return t.length>n?`${t.slice(0,n)}\u2026`:t}function f(e,n,t=0){if(e==null)return e;if(typeof e=="string")return c(e,n);if(typeof e=="number"||typeof e=="boolean")return e;if(t>=4)return"[Truncated]";if(Array.isArray(e))return e.slice(0,20).map(i=>f(i,n,t+1));if(typeof e=="object"){const i={};for(const[s,a]of Object.entries(e).slice(0,50))i[s]=D.test(s)?"[REDACTED]":f(a,n,t+1);return i}return String(e)}function x(e,n){const t=N(e)?.command;if(typeof t=="string")return c(t,n);if(Array.isArray(t))return c(t.map(String).join(" "),n)}function C(e){return Buffer.byteLength(JSON.stringify(e),"utf8")}function E(e){for(let n=0;n<3;n++){const t=C(e);if(t===e.byteSize)break;e.byteSize=t}return e}function I(e,n){const t={...p(m()),sessionId:e.sessionId,toolCallId:e.toolCallId,detailRevision:e.detailRevision,detailStatus:n==="not_found"?"not_found":"unavailable",resultOmitted:!0,redacted:!0,byteSize:0,expiresAt:new Date(Date.now()+l.responseTtlMs).toISOString(),source:"unavailable",reason:n};return E(t)}async function j(e,n){const t=n.params;T(t);const i=typeof t.sessionId=="string"?t.sessionId:"",s=typeof t.toolCallId=="string"?t.toolCallId:"",a=typeof t.detailRevision=="number"&&Number.isSafeInteger(t.detailRevision)?t.detailRevision:-1;if(!i||!s||a<0){e.client.sendRes({type:"res",id:n.id,ok:!1,error:"upgrade_required: sessionId, toolCallId and detailRevision are required"});return}const u=k(t.maxBytes),y={...p(m()),sessionId:i,toolCallId:s,detailRevision:a,maxBytes:u},r=_(i,s);if(!r){e.client.sendRes({type:"res",id:n.id,ok:!0,payload:I(y,"not_found")});return}if(r.detailRevision!==a){e.client.sendRes({type:"res",id:n.id,ok:!0,payload:I(y,"revision_mismatch")});return}const d=Math.max(512,Math.floor(u/4)),b=r.args==null?void 0:f(r.args,d),g=x(r.args,d),S=typeof r.result=="string"?c(r.result,d):r.result==null?void 0:c(JSON.stringify(f(r.result,d)),d),R=new Date(Date.now()+l.responseTtlMs).toISOString();let o={...p(m()),sessionId:i,toolCallId:s,detailRevision:a,detailStatus:"available",status:r.status,...g?{command:g}:{},...b!==void 0?{argsSummary:b}:{},...S!==void 0?{result:S}:{},resultOmitted:S===void 0,redacted:!0,byteSize:0,expiresAt:R,source:"local-tool-index"};E(o),o.byteSize>u&&(delete o.result,o.resultOmitted=!0,E(o)),o.byteSize>u&&(o=I(y,"response_too_large")),e.client.sendRes({type:"res",id:n.id,ok:!0,payload:o})}export{j as handleSessionToolDetail};
|
|
@@ -1,62 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// @test ../../__tests__/voice-window.test.ts
|
|
3
|
-
import { extractPayloadText, isAgentHiddenPayload, isSystemControlPayload, isToolPayload, } from '@shennian/wire';
|
|
4
|
-
import { getCanonicalSessionStore, getDaemonInstallationId } from '../store.js';
|
|
5
|
-
import { assertPersonalSyncIdentity, getPersonalSyncIdentity } from '../../personal-sync/epoch.js';
|
|
6
|
-
const MAX_MESSAGES = 8;
|
|
7
|
-
const MAX_BYTES = 32 * 1024;
|
|
8
|
-
export function handleVoiceSessionBodyWindow(runtime, req) {
|
|
9
|
-
const params = req.params;
|
|
10
|
-
assertPersonalSyncIdentity(params);
|
|
11
|
-
if (!params?.sessionId?.trim() || !params.callId?.trim()) {
|
|
12
|
-
throw new Error('sessionId and callId are required');
|
|
13
|
-
}
|
|
14
|
-
const limit = boundedInteger(params.limit, 4, MAX_MESSAGES);
|
|
15
|
-
const maxBytes = boundedInteger(params.maxBytes, MAX_BYTES, MAX_BYTES);
|
|
16
|
-
const page = getCanonicalSessionStore().readRecent(params.sessionId, Math.min(limit * 6, 48));
|
|
17
|
-
const visible = page.messages.filter(isVoiceReadableMessage);
|
|
18
|
-
const selected = [];
|
|
19
|
-
let bytes = 0;
|
|
20
|
-
for (let index = visible.length - 1; index >= 0 && selected.length < limit; index -= 1) {
|
|
21
|
-
const message = visible[index];
|
|
22
|
-
const size = Buffer.byteLength(JSON.stringify(message), 'utf8');
|
|
23
|
-
if (selected.length > 0 && bytes + size > maxBytes)
|
|
24
|
-
break;
|
|
25
|
-
if (size > maxBytes)
|
|
26
|
-
continue;
|
|
27
|
-
selected.push(message);
|
|
28
|
-
bytes += size;
|
|
29
|
-
}
|
|
30
|
-
selected.reverse();
|
|
31
|
-
runtime.client.sendRes({
|
|
32
|
-
type: 'res',
|
|
33
|
-
id: req.id,
|
|
34
|
-
ok: true,
|
|
35
|
-
payload: {
|
|
36
|
-
...getPersonalSyncIdentity(getDaemonInstallationId()),
|
|
37
|
-
sessionId: params.sessionId,
|
|
38
|
-
callId: params.callId,
|
|
39
|
-
bodyRevision: page.bodyRevision,
|
|
40
|
-
messages: selected,
|
|
41
|
-
truncated: page.hasOlder || selected.length < visible.length,
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
function isVoiceReadableMessage(message) {
|
|
46
|
-
if (message.role !== 'user' && message.role !== 'agent')
|
|
47
|
-
return false;
|
|
48
|
-
if (isToolPayload(message.payload))
|
|
49
|
-
return false;
|
|
50
|
-
if (isSystemControlPayload(message.payload))
|
|
51
|
-
return false;
|
|
52
|
-
if (isAgentHiddenPayload(message.payload))
|
|
53
|
-
return false;
|
|
54
|
-
return extractPayloadText(message.payload).trim().length > 0;
|
|
55
|
-
}
|
|
56
|
-
function boundedInteger(value, fallback, max) {
|
|
57
|
-
if (value === undefined)
|
|
58
|
-
return fallback;
|
|
59
|
-
if (!Number.isSafeInteger(value) || value < 1)
|
|
60
|
-
throw new Error('invalid voice window limit');
|
|
61
|
-
return Math.min(value, max);
|
|
62
|
-
}
|
|
1
|
+
import{extractPayloadText as u,isAgentHiddenPayload as g,isSystemControlPayload as I,isToolPayload as h}from"@shennian/wire";import{getCanonicalSessionStore as p,getDaemonInstallationId as S}from"../store.js";import{assertPersonalSyncIdentity as b,getPersonalSyncIdentity as w}from"../../personal-sync/epoch.js";const x=8,y=32*1024;function R(e,i){const t=i.params;if(b(t),!t?.sessionId?.trim()||!t.callId?.trim())throw new Error("sessionId and callId are required");const l=m(t.limit,4,x),d=m(t.maxBytes,y,y),o=p().readRecent(t.sessionId,Math.min(l*6,48)),s=o.messages.filter(M),n=[];let c=0;for(let r=s.length-1;r>=0&&n.length<l;r-=1){const f=s[r],a=Buffer.byteLength(JSON.stringify(f),"utf8");if(n.length>0&&c+a>d)break;a>d||(n.push(f),c+=a)}n.reverse(),e.client.sendRes({type:"res",id:i.id,ok:!0,payload:{...w(S()),sessionId:t.sessionId,callId:t.callId,bodyRevision:o.bodyRevision,messages:n,truncated:o.hasOlder||n.length<s.length}})}function M(e){return e.role!=="user"&&e.role!=="agent"||h(e.payload)||I(e.payload)||g(e.payload)?!1:u(e.payload).trim().length>0}function m(e,i,t){if(e===void 0)return i;if(!Number.isSafeInteger(e)||e<1)throw new Error("invalid voice window limit");return Math.min(e,t)}export{R as handleVoiceSessionBodyWindow};
|