shennian 0.3.84 → 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.
Files changed (99) hide show
  1. package/dist/bin/shennian.js +1 -1
  2. package/dist/publish-build-manifest.json +30 -30
  3. package/dist/src/agents/adapter.js +19 -1
  4. package/dist/src/agents/claude-managed-permissions.js +248 -1
  5. package/dist/src/agents/claude.js +393 -8
  6. package/dist/src/agents/codex-managed-permissions.js +294 -1
  7. package/dist/src/agents/codex-managed-room-tools.js +249 -1
  8. package/dist/src/agents/codex-utils.js +200 -7
  9. package/dist/src/agents/codex.js +1352 -17
  10. package/dist/src/agents/config-status.js +226 -1
  11. package/dist/src/agents/cursor-managed-permissions.js +299 -2
  12. package/dist/src/agents/cursor.js +314 -1
  13. package/dist/src/agents/custom.js +418 -4
  14. package/dist/src/agents/external-channel-instructions.js +94 -11
  15. package/dist/src/agents/gemini.js +180 -1
  16. package/dist/src/agents/manager.d.ts +2 -0
  17. package/dist/src/agents/manager.js +250 -18
  18. package/dist/src/agents/model-registry/cache.js +37 -1
  19. package/dist/src/agents/model-registry/discovery.js +188 -2
  20. package/dist/src/agents/model-registry/parsers.js +521 -4
  21. package/dist/src/agents/model-registry/runner.js +30 -1
  22. package/dist/src/agents/model-registry/service.js +115 -1
  23. package/dist/src/agents/model-registry/types.js +8 -1
  24. package/dist/src/agents/model-registry.js +19 -1
  25. package/dist/src/agents/native-capabilities.js +237 -1
  26. package/dist/src/agents/openclaw.js +293 -2
  27. package/dist/src/agents/opencode-managed-permissions.js +187 -2
  28. package/dist/src/agents/opencode.js +298 -1
  29. package/dist/src/agents/platform-instructions.js +46 -17
  30. package/dist/src/agents/runtime-compatibility.js +44 -1
  31. package/dist/src/agents/workbuddy-managed-permissions.js +329 -2
  32. package/dist/src/agents/workbuddy.js +444 -3
  33. package/dist/src/commands/room.js +2 -2
  34. package/dist/src/fs/boundary.js +185 -1
  35. package/dist/src/fs/handler.js +130 -1
  36. package/dist/src/fs/security.js +32 -1
  37. package/dist/src/fs/text-decoder.js +110 -1
  38. package/dist/src/index.js +587 -3
  39. package/dist/src/manager/prompt.js +34 -29
  40. package/dist/src/manager/registry.js +317 -1
  41. package/dist/src/manager/retired-external-channel-compat.js +40 -1
  42. package/dist/src/manager/runtime.d.ts +4 -0
  43. package/dist/src/manager/runtime.js +1514 -23
  44. package/dist/src/native-fusion/codex-parser.js +660 -4
  45. package/dist/src/native-fusion/codex-user-input.js +102 -5
  46. package/dist/src/native-fusion/config.js +5 -1
  47. package/dist/src/native-fusion/file-watcher.js +191 -1
  48. package/dist/src/native-fusion/local-repository.js +569 -2
  49. package/dist/src/native-fusion/opencode-parser.js +130 -3
  50. package/dist/src/native-fusion/parsers.js +140 -3
  51. package/dist/src/native-fusion/service.js +739 -2
  52. package/dist/src/native-fusion/session-header.js +101 -2
  53. package/dist/src/native-fusion/state.js +104 -2
  54. package/dist/src/native-fusion/types.js +1 -1
  55. package/dist/src/personal-sync/machine-authority.js +34 -1
  56. package/dist/src/room/activation-audit.js +45 -2
  57. package/dist/src/room/activation-policy.js +30 -1
  58. package/dist/src/room/activation-service.d.ts +1 -0
  59. package/dist/src/room/activation-service.js +408 -8
  60. package/dist/src/room/invite-bootstrap.js +156 -1
  61. package/dist/src/room/managed-context.d.ts +1 -1
  62. package/dist/src/room/managed-context.js +1 -1
  63. package/dist/src/room/managed-prompt-composer.d.ts +1 -1
  64. package/dist/src/room/managed-prompt-composer.js +6 -5
  65. package/dist/src/room/managed-room-binding.js +167 -1
  66. package/dist/src/room/managed-room-ipc.d.ts +2 -2
  67. package/dist/src/room/managed-room-ipc.js +2 -2
  68. package/dist/src/room/mirror-sync-service.js +183 -1
  69. package/dist/src/room/room-action-client.d.ts +11 -0
  70. package/dist/src/room/room-action-client.js +1 -1
  71. package/dist/src/room/room-binding-client.d.ts +1 -0
  72. package/dist/src/room/room-binding-client.js +151 -1
  73. package/dist/src/security/managed-agent-policy.d.ts +3 -2
  74. package/dist/src/security/managed-agent-policy.js +1 -1
  75. package/dist/src/session/archive-zip.js +220 -1
  76. package/dist/src/session/deletion-sync-state.js +136 -2
  77. package/dist/src/session/handlers/agent-capabilities.js +14 -1
  78. package/dist/src/session/handlers/agent-config.js +150 -1
  79. package/dist/src/session/handlers/agents.js +55 -1
  80. package/dist/src/session/handlers/chat.d.ts +1 -1
  81. package/dist/src/session/handlers/chat.js +1030 -2
  82. package/dist/src/session/handlers/control.js +61 -1
  83. package/dist/src/session/handlers/fs.js +594 -1
  84. package/dist/src/session/handlers/message-status.js +15 -1
  85. package/dist/src/session/handlers/remote-access.js +43 -1
  86. package/dist/src/session/handlers/room-managed.js +101 -1
  87. package/dist/src/session/handlers/session-refresh.js +35 -1
  88. package/dist/src/session/handlers/title.js +79 -1
  89. package/dist/src/session/handlers/tool-detail.js +149 -1
  90. package/dist/src/session/handlers/voice-window.js +62 -1
  91. package/dist/src/session/history-subscriptions.js +470 -1
  92. package/dist/src/session/manager.js +658 -1
  93. package/dist/src/session/native-cleanup-outbox.js +287 -2
  94. package/dist/src/session/native-session-lifecycle.js +327 -6
  95. package/dist/src/session/queue.d.ts +5 -0
  96. package/dist/src/session/queue.js +8 -8
  97. package/dist/src/upgrade/scheduler.js +126 -1
  98. package/node_modules/@shennian/wire/dist/agent-workspace.js +1 -1
  99. package/package.json +1 -1
@@ -1 +1,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
+ // @arch docs/architecture/data-sync.md#8-发送确认与离线边界
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 +1,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
+ // @arch docs/features/platform-security-boundary-and-hardening-todo.md
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 +1,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
+ // @arch docs/features/network-rooms/multi-session-client-and-room-panel.md
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 +1,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
+ // @arch docs/architecture/cli/native-session-fusion.md
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 +1,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
+ // @arch docs/architecture/cli/daemon.md#会话管理
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 +1,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
+ // @arch docs/architecture/data-sync.md#工具详情按需读取
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 +1,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};
1
+ // @arch docs/architecture/data-sync.md#6-voice-shennian-边界
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
+ }