shennian 0.3.41 → 0.3.43

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 (75) hide show
  1. package/dist/publish-build-manifest.json +93 -43
  2. package/dist/src/agents/adapter.d.ts +1 -0
  3. package/dist/src/commands/daemon.d.ts +6 -0
  4. package/dist/src/commands/daemon.js +9 -9
  5. package/dist/src/index.js +3 -3
  6. package/dist/src/native-fusion/codex-parser.d.ts +1 -0
  7. package/dist/src/native-fusion/codex-parser.js +4 -4
  8. package/dist/src/native-fusion/file-watcher.d.ts +18 -0
  9. package/dist/src/native-fusion/file-watcher.js +1 -0
  10. package/dist/src/native-fusion/local-repository.d.ts +52 -0
  11. package/dist/src/native-fusion/local-repository.js +2 -0
  12. package/dist/src/native-fusion/opencode-parser.js +2 -2
  13. package/dist/src/native-fusion/parser-common.d.ts +7 -1
  14. package/dist/src/native-fusion/parser-common.js +4 -4
  15. package/dist/src/native-fusion/parsers.d.ts +2 -1
  16. package/dist/src/native-fusion/parsers.js +2 -2
  17. package/dist/src/native-fusion/service.d.ts +10 -2
  18. package/dist/src/native-fusion/service.js +2 -2
  19. package/dist/src/native-fusion/session-header.d.ts +9 -0
  20. package/dist/src/native-fusion/session-header.js +2 -0
  21. package/dist/src/native-fusion/state.d.ts +1 -1
  22. package/dist/src/native-fusion/state.js +2 -1
  23. package/dist/src/native-fusion/types.d.ts +29 -5
  24. package/dist/src/native-fusion/types.js +1 -1
  25. package/dist/src/personal-sync/paths.d.ts +2 -0
  26. package/dist/src/personal-sync/paths.js +1 -0
  27. package/dist/src/relay/client.d.ts +4 -1
  28. package/dist/src/relay/client.js +1 -1
  29. package/dist/src/session/attachment-reference.d.ts +2 -0
  30. package/dist/src/session/attachment-reference.js +1 -0
  31. package/dist/src/session/attachment-transfer-store.d.ts +38 -0
  32. package/dist/src/session/attachment-transfer-store.js +2 -0
  33. package/dist/src/session/canonical-store.d.ts +101 -0
  34. package/dist/src/session/canonical-store.js +5 -0
  35. package/dist/src/session/handlers/chat.js +2 -2
  36. package/dist/src/session/handlers/fs.d.ts +0 -1
  37. package/dist/src/session/handlers/fs.js +1 -1
  38. package/dist/src/session/handlers/message-status.d.ts +3 -0
  39. package/dist/src/session/handlers/message-status.js +1 -0
  40. package/dist/src/session/handlers/title.js +1 -1
  41. package/dist/src/session/handlers/tool-detail.js +1 -1
  42. package/dist/src/session/handlers/voice-window.d.ts +3 -0
  43. package/dist/src/session/handlers/voice-window.js +1 -0
  44. package/dist/src/session/history-subscriptions.d.ts +17 -0
  45. package/dist/src/session/history-subscriptions.js +1 -0
  46. package/dist/src/session/index-publisher.d.ts +3 -0
  47. package/dist/src/session/index-publisher.js +1 -0
  48. package/dist/src/session/manager.d.ts +2 -0
  49. package/dist/src/session/manager.js +1 -1
  50. package/dist/src/session/queue.d.ts +1 -1
  51. package/dist/src/session/queue.js +4 -3
  52. package/dist/src/session/store.d.ts +33 -3
  53. package/dist/src/session/store.js +2 -3
  54. package/dist/src/session/tool-detail-store.d.ts +16 -0
  55. package/dist/src/session/tool-detail-store.js +2 -0
  56. package/dist/src/session/types.d.ts +3 -13
  57. package/node_modules/@shennian/wire/dist/events.d.ts +2 -0
  58. package/node_modules/@shennian/wire/dist/fs.d.ts +45 -0
  59. package/node_modules/@shennian/wire/dist/fs.js +7 -1
  60. package/node_modules/@shennian/wire/dist/index.d.ts +1 -0
  61. package/node_modules/@shennian/wire/dist/index.js +1 -0
  62. package/node_modules/@shennian/wire/dist/machine.d.ts +6 -0
  63. package/node_modules/@shennian/wire/dist/message-payload.d.ts +2 -0
  64. package/node_modules/@shennian/wire/dist/message-payload.js +15 -1
  65. package/node_modules/@shennian/wire/dist/methods.d.ts +1 -1
  66. package/node_modules/@shennian/wire/dist/session-sync.d.ts +197 -0
  67. package/node_modules/@shennian/wire/dist/session-sync.js +49 -0
  68. package/node_modules/@shennian/wire/dist/session.d.ts +24 -13
  69. package/node_modules/@shennian/wire/dist/tool-detail.d.ts +18 -11
  70. package/node_modules/@shennian/wire/dist/tool-detail.js +7 -1
  71. package/package.json +1 -1
  72. package/dist/src/session/remote-attachments.d.ts +0 -15
  73. package/dist/src/session/remote-attachments.js +0 -1
  74. package/dist/src/session/tool-summary.d.ts +0 -1
  75. package/dist/src/session/tool-summary.js +0 -1
@@ -25,9 +25,54 @@ export type FsChunk = {
25
25
  export type FsTransferManifestEntry = {
26
26
  relativePath: string;
27
27
  size: number;
28
+ sha256: string;
28
29
  mimeType?: string;
29
30
  modifiedAt?: number;
30
31
  };
32
+ export type FsTransferStartParams = {
33
+ transferId: string;
34
+ rootPath: string;
35
+ name: string;
36
+ totalSize: number;
37
+ sha256: string;
38
+ mimeType: string;
39
+ kind: 'file' | 'folder';
40
+ manifest?: FsTransferManifestEntry[];
41
+ };
42
+ export type FsTransferStartResult = {
43
+ transferId: string;
44
+ path: string;
45
+ reused: boolean;
46
+ offsets: Record<string, number>;
47
+ attachment?: FsTransferFinishResult;
48
+ };
49
+ export type FsTransferChunkParams = {
50
+ transferId: string;
51
+ rootPath: string;
52
+ offset: number;
53
+ data: string;
54
+ relativePath?: string;
55
+ };
56
+ export type FsTransferFinishParams = {
57
+ transferId: string;
58
+ rootPath: string;
59
+ };
60
+ export type FsTransferFinishResult = {
61
+ transferId: string;
62
+ path: string;
63
+ name: string;
64
+ mimeType: string;
65
+ kind: 'file' | 'folder';
66
+ byteSize: number;
67
+ sha256: string;
68
+ reused: boolean;
69
+ };
70
+ export type FsTransferAbortParams = {
71
+ transferId: string;
72
+ rootPath: string;
73
+ };
74
+ /** Stable input for the aggregate hash of a folder transfer. */
75
+ export declare function serializeFsTransferManifest(entries: readonly FsTransferManifestEntry[]): string;
31
76
  export type FsRenameResult = {
32
77
  oldPath: string;
33
78
  newPath: string;
@@ -1 +1,7 @@
1
- export {};
1
+ /** Stable input for the aggregate hash of a folder transfer. */
2
+ export function serializeFsTransferManifest(entries) {
3
+ return [...entries]
4
+ .sort((left, right) => left.relativePath.localeCompare(right.relativePath))
5
+ .map((entry) => `${entry.relativePath}\0${entry.size}\0${entry.sha256.toLowerCase()}`)
6
+ .join('\n');
7
+ }
@@ -5,6 +5,7 @@ export * from './methods.ts';
5
5
  export * from './frames.ts';
6
6
  export * from './fs.ts';
7
7
  export * from './session.ts';
8
+ export * from './session-sync.ts';
8
9
  export * from './message-payload.ts';
9
10
  export * from './external-message-format.ts';
10
11
  export * from './native-session.ts';
@@ -5,6 +5,7 @@ export * from "./methods.js";
5
5
  export * from "./frames.js";
6
6
  export * from "./fs.js";
7
7
  export * from "./session.js";
8
+ export * from "./session-sync.js";
8
9
  export * from "./message-payload.js";
9
10
  export * from "./external-message-format.js";
10
11
  export * from "./native-session.js";
@@ -14,6 +14,12 @@ export type Machine = {
14
14
  region?: string;
15
15
  /** CLI version currently running on this machine */
16
16
  cliVersion?: string;
17
+ /** Stable identity of the daemon data root. It is not the mobile installationId. */
18
+ daemonInstallationId?: string;
19
+ /** Personal Session sync protocol understood by this daemon. */
20
+ sessionSyncProtocolVersion?: import('./session-sync.ts').SessionSyncProtocolVersion;
21
+ /** Full capability set required by the hard-cutover personal Session protocol. */
22
+ sessionSyncCapabilities?: import('./session-sync.ts').SessionSyncCapability[];
17
23
  /** Auto-upgrade policy configured on this machine */
18
24
  autoUpgrade?: AutoUpgradePolicy;
19
25
  /** Ownership role — absent means owner (backward-compatible) */
@@ -4,6 +4,8 @@ export type ChatAttachmentMeta = {
4
4
  name: string;
5
5
  mimeType: string;
6
6
  kind: ChatAttachmentKind;
7
+ byteSize?: number;
8
+ sha256?: string;
7
9
  previewData?: string;
8
10
  };
9
11
  export type StructuredUserMessagePayload = {
@@ -52,7 +52,21 @@ function parseAttachmentMeta(value) {
52
52
  if (!path || !name || !mimeType || !kind)
53
53
  return null;
54
54
  const previewData = typeof entry.previewData === 'string' && entry.previewData.trim() ? entry.previewData.trim() : undefined;
55
- return { path, name, mimeType, kind, ...(previewData ? { previewData } : {}) };
55
+ const byteSize = typeof entry.byteSize === 'number' && Number.isSafeInteger(entry.byteSize) && entry.byteSize >= 0
56
+ ? entry.byteSize
57
+ : undefined;
58
+ const sha256 = typeof entry.sha256 === 'string' && /^[a-f0-9]{64}$/i.test(entry.sha256)
59
+ ? entry.sha256.toLowerCase()
60
+ : undefined;
61
+ return {
62
+ path,
63
+ name,
64
+ mimeType,
65
+ kind,
66
+ ...(byteSize !== undefined ? { byteSize } : {}),
67
+ ...(sha256 ? { sha256 } : {}),
68
+ ...(previewData ? { previewData } : {}),
69
+ };
56
70
  }
57
71
  export function parseStructuredMessagePayload(payload) {
58
72
  const parsed = parseObjectPayload(payload);
@@ -1 +1 @@
1
- export type ReqMethod = 'chat.send' | 'chat.enqueue' | 'chat.queue.get' | 'chat.queue.edit' | 'chat.queue.delete' | 'chat.abort' | 'session.resume' | 'session.history' | 'session.refresh' | 'session.tool.detail' | 'session.title.set' | 'fs.ls' | 'fs.read' | 'fs.write' | 'fs.export.markdown-pdf' | 'fs.export.markdown-pdf.setup' | 'fs.archive.zip' | 'fs.transfer' | 'fs.transfer.start' | 'fs.transfer.chunk' | 'fs.transfer.finish' | 'fs.transfer.abort' | 'fs.rename' | 'skill.list' | 'skill.install' | 'skill.doctor' | 'skill.setup' | 'skill.use' | 'region.probe' | 'region.switch' | 'upgrade.start' | 'upgrade.status' | 'upgrade.set-policy' | 'agents.refresh' | 'models.refresh' | 'agent.config.get' | 'agent.config.upsert' | 'agent.config.clear' | 'agent.config.test' | 'agent.capabilities.list' | 'manager.channel.get' | 'manager.channel.upsert' | 'session.wechat-rpa.channel.get' | 'session.wechat-rpa.channel.upsert' | 'session.wechat-rpa.channel.sync' | 'session.wechat-rpa.outbound.cancel' | 'manager.wechat-rpa.channel.get' | 'manager.wechat-rpa.channel.upsert' | 'manager.wechat-rpa.channel.sync' | 'manager.wechat-rpa.outbound.cancel' | 'wechat-rpa.helper.runtime' | 'wecom.send' | 'external.send' | 'room.sync' | 'room.send' | 'room.bind' | 'room.agent.bind-managed' | 'room.agent.restart-managed' | 'room.unbind';
1
+ export type ReqMethod = 'chat.send' | 'chat.enqueue' | 'chat.queue.get' | 'chat.queue.edit' | 'chat.queue.delete' | 'chat.abort' | 'session.resume' | 'session.history' | 'session.history.open' | 'session.history.page' | 'session.history.close' | 'session.index.update' | 'session.message.status' | 'voice.session.body.window' | 'session.refresh' | 'session.tool.detail' | 'session.title.set' | 'fs.ls' | 'fs.read' | 'fs.write' | 'fs.export.markdown-pdf' | 'fs.export.markdown-pdf.setup' | 'fs.archive.zip' | 'fs.transfer.start' | 'fs.transfer.chunk' | 'fs.transfer.finish' | 'fs.transfer.abort' | 'fs.rename' | 'skill.list' | 'skill.install' | 'skill.doctor' | 'skill.setup' | 'skill.use' | 'region.probe' | 'region.switch' | 'upgrade.start' | 'upgrade.status' | 'upgrade.set-policy' | 'agents.refresh' | 'models.refresh' | 'agent.config.get' | 'agent.config.upsert' | 'agent.config.clear' | 'agent.config.test' | 'agent.capabilities.list' | 'manager.channel.get' | 'manager.channel.upsert' | 'session.wechat-rpa.channel.get' | 'session.wechat-rpa.channel.upsert' | 'session.wechat-rpa.channel.sync' | 'session.wechat-rpa.outbound.cancel' | 'manager.wechat-rpa.channel.get' | 'manager.wechat-rpa.channel.upsert' | 'manager.wechat-rpa.channel.sync' | 'manager.wechat-rpa.outbound.cancel' | 'wechat-rpa.helper.runtime' | 'wecom.send' | 'external.send' | 'room.sync' | 'room.send' | 'room.bind' | 'room.agent.bind-managed' | 'room.agent.restart-managed' | 'room.unbind';
@@ -0,0 +1,197 @@
1
+ import type { Envelope } from './envelope.ts';
2
+ import type { AgentType, ManagerConfig, SessionMode } from './session.ts';
3
+ export declare const SESSION_SYNC_PROTOCOL_VERSION: 1;
4
+ export type SessionSyncProtocolVersion = typeof SESSION_SYNC_PROTOCOL_VERSION;
5
+ export declare const SESSION_SYNC_RELEASE_GATE: {
6
+ readonly minCliVersion: "0.3.42";
7
+ readonly minAppBuild: {
8
+ readonly ios: 51;
9
+ readonly android: 12;
10
+ };
11
+ };
12
+ export declare const SESSION_SYNC_REQUIRED_CAPABILITIES: readonly ["session_index_v1", "session_body_revision_v1", "session_history_subscription_v1", "local_canonical_v1", "session_title_authority_v1", "session_tool_detail_v1", "voice_session_body_relay_v1", "native_source_tombstone_v1", "session_durable_send_v1", "daemon_installation_identity_v1"];
13
+ export declare const SESSION_SYNC_EVENT_NAMES: readonly ["session.index.updated", "session.body.delta"];
14
+ export declare const SESSION_HISTORY_LIMITS: {
15
+ readonly defaultSnapshotMessages: 100;
16
+ readonly maxSnapshotMessages: 500;
17
+ readonly defaultPageMessages: 100;
18
+ readonly maxPageMessages: 500;
19
+ readonly maxDeltaMutations: 200;
20
+ readonly maxMutationBytes: number;
21
+ readonly maxSubscriptionsPerClient: 4;
22
+ readonly maxSubscriptionsPerSession: 8;
23
+ };
24
+ export type SessionSyncCapability = (typeof SESSION_SYNC_REQUIRED_CAPABILITIES)[number];
25
+ export type SessionSyncEventName = (typeof SESSION_SYNC_EVENT_NAMES)[number];
26
+ export type SessionBodyStorageMode = 'machine_local';
27
+ export type SessionTitleSource = 'shennian_manual' | 'agent_native' | 'shennian_generated' | 'first_user_fallback';
28
+ export declare const SESSION_TITLE_SOURCE_PRIORITY: Readonly<Record<SessionTitleSource, number>>;
29
+ export declare function isSessionTitleSource(value: unknown): value is SessionTitleSource;
30
+ export type SessionTitleSetParams = {
31
+ sessionId: string;
32
+ agentType: AgentType;
33
+ agentSessionId: string | null;
34
+ workDir: string;
35
+ title: string;
36
+ titleSource: 'shennian_manual';
37
+ titleRevision: number;
38
+ titleOriginEventId: string;
39
+ };
40
+ export type SessionSyncErrorCode = 'upgrade_required' | 'revision_gap' | 'snapshot_compacted' | 'machine_offline' | 'history_unavailable' | 'subscription_not_found' | 'subscription_limit_exceeded' | 'response_too_large';
41
+ export type SessionSyncError = {
42
+ code: SessionSyncErrorCode;
43
+ message?: string;
44
+ requiredProtocolVersion?: SessionSyncProtocolVersion;
45
+ requiredCapabilities?: SessionSyncCapability[];
46
+ expectedRevision?: number;
47
+ actualRevision?: number;
48
+ retryable: boolean;
49
+ };
50
+ export type PersonalAttachmentReference = {
51
+ path: string;
52
+ name: string;
53
+ mimeType: string;
54
+ kind: 'file' | 'image' | 'folder';
55
+ byteSize: number;
56
+ sha256: string;
57
+ };
58
+ export type SessionDeliveryState = 'queued' | 'committed' | 'executing' | 'completed' | 'failed' | 'cancelled';
59
+ export type SessionDurableSendAck<TState extends SessionDeliveryState = SessionDeliveryState> = {
60
+ sessionId: string;
61
+ clientMessageId: string;
62
+ messageId: string;
63
+ deliveryState: TState;
64
+ bodyRevision: number;
65
+ committedAt: string;
66
+ };
67
+ export type SessionMessageStatusParams = {
68
+ sessionId: string;
69
+ clientMessageId: string;
70
+ };
71
+ export type SessionMessageStatusResponse = {
72
+ state: 'not_found';
73
+ sessionId: string;
74
+ clientMessageId: string;
75
+ } | {
76
+ [TState in SessionDeliveryState]: {
77
+ state: TState;
78
+ } & SessionDurableSendAck<TState>;
79
+ }[SessionDeliveryState];
80
+ export declare function getMissingSessionSyncCapabilities(capabilities: readonly string[] | null | undefined): SessionSyncCapability[];
81
+ export declare function hasRequiredSessionSyncCapabilities(capabilities: readonly string[] | null | undefined): capabilities is readonly SessionSyncCapability[];
82
+ export type SessionHistoryOpenParams = {
83
+ sessionId: string;
84
+ subscriptionId: string;
85
+ cachedBodyRevision?: number;
86
+ limit?: number;
87
+ beforeSequence?: number;
88
+ };
89
+ export type SessionHistoryMessage = {
90
+ sequence: number;
91
+ revision: number;
92
+ message: Envelope;
93
+ };
94
+ export type SessionHistorySnapshot = {
95
+ sessionId: string;
96
+ subscriptionId: string;
97
+ storageMode: SessionBodyStorageMode;
98
+ snapshotRevision: number;
99
+ firstAvailableSeq: number | null;
100
+ lastAvailableSeq: number | null;
101
+ hasOlder: boolean;
102
+ messages: SessionHistoryMessage[];
103
+ };
104
+ export type SessionHistoryPageParams = {
105
+ sessionId: string;
106
+ subscriptionId: string;
107
+ beforeSequence: number;
108
+ limit?: number;
109
+ };
110
+ export type SessionHistoryPage = {
111
+ sessionId: string;
112
+ subscriptionId: string;
113
+ snapshotRevision: number;
114
+ firstAvailableSeq: number | null;
115
+ hasOlder: boolean;
116
+ messages: SessionHistoryMessage[];
117
+ };
118
+ export type VoiceSessionBodyWindowParams = {
119
+ sessionId: string;
120
+ callId: string;
121
+ bodyRevision?: number;
122
+ limit?: number;
123
+ maxBytes?: number;
124
+ };
125
+ export type VoiceSessionBodyWindow = {
126
+ sessionId: string;
127
+ callId: string;
128
+ bodyRevision: number;
129
+ messages: Envelope[];
130
+ truncated: boolean;
131
+ };
132
+ export type SessionHistoryCloseParams = {
133
+ sessionId: string;
134
+ subscriptionId: string;
135
+ };
136
+ export type SessionHistoryCloseResponse = {
137
+ sessionId: string;
138
+ subscriptionId: string;
139
+ closed: true;
140
+ };
141
+ export type SessionBodyMutation = {
142
+ sessionId: string;
143
+ subscriptionId: string;
144
+ revision: number;
145
+ mutation: 'append' | 'upsert' | 'tombstone';
146
+ messageId: string;
147
+ sequence: number;
148
+ message?: Envelope;
149
+ deliveryState?: SessionDeliveryState;
150
+ };
151
+ export type SessionBodyDeltaEvent = {
152
+ sessionId: string;
153
+ subscriptionId: string;
154
+ fromRevision: number;
155
+ toRevision: number;
156
+ mutations: SessionBodyMutation[];
157
+ };
158
+ /**
159
+ * Machine-authored personal-session projection. Ownership, machine display
160
+ * fields and viewer permissions are server authority and therefore never
161
+ * accepted from a daemon payload.
162
+ */
163
+ export type DaemonSessionIndexProjection = {
164
+ id: string;
165
+ agentType: AgentType;
166
+ sessionMode?: SessionMode;
167
+ managerConfig?: ManagerConfig | null;
168
+ agentSessionId: string | null;
169
+ modelId: string | null;
170
+ managerDefaultWorkerAgentType?: AgentType | null;
171
+ managerDefaultWorkerModelId?: string | null;
172
+ title: string;
173
+ titleSource: SessionTitleSource;
174
+ titleRevision: number;
175
+ titleLockedByUser: boolean;
176
+ titleOriginEventId: string | null;
177
+ status: 'active' | 'completed' | 'failed';
178
+ workDir: string;
179
+ lastMessagePreview: string | null;
180
+ bodyStorageMode: SessionBodyStorageMode;
181
+ bodyRevision: number;
182
+ indexRevision: number;
183
+ firstAvailableSeq: number | null;
184
+ visibleMessageCount: number;
185
+ lastVisibleMessageId: string | null;
186
+ lastBodyCommittedAt: string | null;
187
+ deletionRevision: number;
188
+ lastActivityAt: string;
189
+ createdAt: string;
190
+ updatedAt: string;
191
+ deletedAt: string | null;
192
+ };
193
+ export type SessionIndexUpdate = {
194
+ protocolVersion: SessionSyncProtocolVersion;
195
+ daemonInstallationId: string;
196
+ session: DaemonSessionIndexProjection;
197
+ };
@@ -0,0 +1,49 @@
1
+ // @arch docs/architecture/data-sync.md#目标架构-v3
2
+ // @test src/__tests__/session-sync.test.ts
3
+ export const SESSION_SYNC_PROTOCOL_VERSION = 1;
4
+ export const SESSION_SYNC_RELEASE_GATE = {
5
+ minCliVersion: '0.3.42',
6
+ minAppBuild: { ios: 51, android: 12 },
7
+ };
8
+ export const SESSION_SYNC_REQUIRED_CAPABILITIES = [
9
+ 'session_index_v1',
10
+ 'session_body_revision_v1',
11
+ 'session_history_subscription_v1',
12
+ 'local_canonical_v1',
13
+ 'session_title_authority_v1',
14
+ 'session_tool_detail_v1',
15
+ 'voice_session_body_relay_v1',
16
+ 'native_source_tombstone_v1',
17
+ 'session_durable_send_v1',
18
+ 'daemon_installation_identity_v1',
19
+ ];
20
+ export const SESSION_SYNC_EVENT_NAMES = [
21
+ 'session.index.updated',
22
+ 'session.body.delta',
23
+ ];
24
+ export const SESSION_HISTORY_LIMITS = {
25
+ defaultSnapshotMessages: 100,
26
+ maxSnapshotMessages: 500,
27
+ defaultPageMessages: 100,
28
+ maxPageMessages: 500,
29
+ maxDeltaMutations: 200,
30
+ maxMutationBytes: 256 * 1024,
31
+ maxSubscriptionsPerClient: 4,
32
+ maxSubscriptionsPerSession: 8,
33
+ };
34
+ export const SESSION_TITLE_SOURCE_PRIORITY = {
35
+ first_user_fallback: 0,
36
+ shennian_generated: 1,
37
+ agent_native: 2,
38
+ shennian_manual: 3,
39
+ };
40
+ export function isSessionTitleSource(value) {
41
+ return typeof value === 'string' && value in SESSION_TITLE_SOURCE_PRIORITY;
42
+ }
43
+ export function getMissingSessionSyncCapabilities(capabilities) {
44
+ const available = new Set(capabilities ?? []);
45
+ return SESSION_SYNC_REQUIRED_CAPABILITIES.filter((capability) => !available.has(capability));
46
+ }
47
+ export function hasRequiredSessionSyncCapabilities(capabilities) {
48
+ return getMissingSessionSyncCapabilities(capabilities).length === 0;
49
+ }
@@ -1,4 +1,5 @@
1
1
  import type { NativeSessionSourceMode } from './native-session.js';
2
+ import type { PersonalAttachmentReference, SessionBodyStorageMode, SessionTitleSource } from './session-sync.js';
2
3
  export type BuiltinAgentType = 'claude' | 'codex' | 'workbuddy' | 'gemini' | 'cursor' | 'openclaw' | 'opencode' | 'pi' | 'manager';
3
4
  export type CustomAgentType = `custom:${string}`;
4
5
  export type AgentType = BuiltinAgentType | CustomAgentType;
@@ -70,6 +71,10 @@ export type SessionUserState = {
70
71
  sessionId: string;
71
72
  pinnedAt?: string | null;
72
73
  lastReadAt?: string | null;
74
+ /** Index watermark captured atomically when the user marks this session read. */
75
+ lastReadBodyRevision?: number;
76
+ lastReadVisibleMessageCount?: number;
77
+ lastReadVisibleMessageId?: string | null;
73
78
  manualUnread?: boolean;
74
79
  updatedAt: string;
75
80
  };
@@ -184,9 +189,21 @@ export type NianSession = {
184
189
  managerDefaultWorkerAgentType?: AgentType | null;
185
190
  managerDefaultWorkerModelId?: string | null;
186
191
  title: string;
192
+ titleSource?: SessionTitleSource;
193
+ titleRevision?: number;
194
+ titleLockedByUser?: boolean;
195
+ titleOriginEventId?: string | null;
187
196
  status: 'active' | 'completed' | 'failed';
188
197
  workDir: string;
189
198
  lastMessagePreview?: string | null;
199
+ bodyStorageMode?: SessionBodyStorageMode;
200
+ bodyRevision?: number;
201
+ indexRevision?: number;
202
+ firstAvailableSeq?: number | null;
203
+ visibleMessageCount?: number;
204
+ lastVisibleMessageId?: string | null;
205
+ lastBodyCommittedAt?: string | null;
206
+ deletionRevision?: number;
190
207
  lastActivityAt: string;
191
208
  createdAt: string;
192
209
  updatedAt: string;
@@ -201,7 +218,7 @@ export type NianSession = {
201
218
  activity?: SessionActivitySnapshot | null;
202
219
  externalChannel?: ExternalChannelSessionStatus | null;
203
220
  };
204
- export type SessionListProjection = Pick<NianSession, 'id' | 'machineId' | 'userId' | 'agentType' | 'sessionMode' | 'managerConfig' | 'agentSessionId' | 'modelId' | 'managerDefaultWorkerAgentType' | 'managerDefaultWorkerModelId' | 'title' | 'status' | 'workDir' | 'lastMessagePreview' | 'lastActivityAt' | 'createdAt' | 'updatedAt' | 'deletedAt' | 'machineName' | 'ownerName' | 'ownerPhone' | 'ownerEmail' | 'canManage' | 'sharedMachineSession' | 'activity' | 'externalChannel'>;
221
+ export type SessionListProjection = Pick<NianSession, 'id' | 'machineId' | 'userId' | 'agentType' | 'sessionMode' | 'managerConfig' | 'agentSessionId' | 'modelId' | 'managerDefaultWorkerAgentType' | 'managerDefaultWorkerModelId' | 'title' | 'titleSource' | 'titleRevision' | 'titleLockedByUser' | 'titleOriginEventId' | 'status' | 'workDir' | 'lastMessagePreview' | 'bodyStorageMode' | 'bodyRevision' | 'indexRevision' | 'firstAvailableSeq' | 'visibleMessageCount' | 'lastVisibleMessageId' | 'lastBodyCommittedAt' | 'deletionRevision' | 'lastActivityAt' | 'createdAt' | 'updatedAt' | 'deletedAt' | 'machineName' | 'ownerName' | 'ownerPhone' | 'ownerEmail' | 'canManage' | 'sharedMachineSession' | 'activity' | 'externalChannel'>;
205
222
  export type ChatSendParams = {
206
223
  sessionId: string;
207
224
  text: string;
@@ -217,10 +234,8 @@ export type ChatSendParams = {
217
234
  reasoningEffort?: string | null;
218
235
  clientMessageId?: string;
219
236
  sessionListProjection?: SessionListProjection[];
220
- attachments?: Array<{
221
- path: string;
222
- name: string;
223
- mimeType: string;
237
+ attachments?: Array<PersonalAttachmentReference & {
238
+ /** @deprecated Removed by the hard-cutover attachment migration. */
224
239
  previewData?: string;
225
240
  }>;
226
241
  externalChannel?: ExternalChannelSessionStatus | null;
@@ -241,10 +256,8 @@ export type QueuedChatMessage = {
241
256
  managerDefaultWorkerModelId?: string | null;
242
257
  reasoningEffort?: string | null;
243
258
  clientMessageId?: string | null;
244
- attachments?: Array<{
245
- path: string;
246
- name: string;
247
- mimeType: string;
259
+ attachments?: Array<PersonalAttachmentReference & {
260
+ /** @deprecated Removed by the hard-cutover attachment migration. */
248
261
  previewData?: string;
249
262
  }>;
250
263
  externalChannel?: ExternalChannelSessionStatus | null;
@@ -268,10 +281,8 @@ export type ChatQueueEditParams = {
268
281
  sessionId: string;
269
282
  queueMessageId: string;
270
283
  text: string;
271
- attachments?: Array<{
272
- path: string;
273
- name: string;
274
- mimeType: string;
284
+ attachments?: Array<PersonalAttachmentReference & {
285
+ /** @deprecated Removed by the hard-cutover attachment migration. */
275
286
  previewData?: string;
276
287
  }>;
277
288
  };
@@ -1,4 +1,12 @@
1
+ export declare const SESSION_TOOL_DETAIL_LIMITS: {
2
+ readonly defaultMaxBytes: number;
3
+ readonly minMaxBytes: number;
4
+ readonly maxMaxBytes: number;
5
+ readonly responseTtlMs: number;
6
+ readonly relayTimeoutMs: number;
7
+ };
1
8
  export type ToolDetailStatus = 'available' | 'not_found' | 'unsupported' | 'unavailable';
9
+ export type ToolDetailUnavailableReason = 'not_found' | 'revision_mismatch' | 'machine_offline' | 'request_timeout' | 'response_too_large' | 'unsupported';
2
10
  export type ToolDetailSource = 'local-session-store' | 'local-tool-index' | 'native-local-index' | 'unavailable';
3
11
  export type ToolDetailRef = {
4
12
  runId?: string;
@@ -7,24 +15,23 @@ export type ToolDetailRef = {
7
15
  };
8
16
  export type SessionToolDetailRequest = {
9
17
  sessionId: string;
10
- messageId: string;
11
- toolIndex?: number;
12
- name?: string;
13
- runId?: string;
14
- sourceSeq?: number;
15
- detailRef?: ToolDetailRef | string | null;
16
- maxChars?: number;
18
+ toolCallId: string;
19
+ detailRevision: number;
20
+ maxBytes?: number;
17
21
  };
18
22
  export type SessionToolDetailResponse = {
23
+ sessionId: string;
24
+ toolCallId: string;
25
+ detailRevision: number;
19
26
  detailStatus: ToolDetailStatus;
20
- messageId?: string;
21
- toolIndex?: number;
22
- name?: string;
23
27
  status?: 'running' | 'completed' | 'failed';
24
28
  command?: string;
25
29
  argsSummary?: unknown;
26
30
  result?: string;
27
31
  resultOmitted: boolean;
32
+ redacted: boolean;
33
+ byteSize: number;
34
+ expiresAt: string;
28
35
  source: ToolDetailSource;
29
- reason?: string;
36
+ reason?: ToolDetailUnavailableReason;
30
37
  };
@@ -1,3 +1,9 @@
1
1
  // @arch docs/architecture/data-retention-and-sync-policy.md#tool-结果
2
2
  // @test src/__tests__/types.test.ts
3
- export {};
3
+ export const SESSION_TOOL_DETAIL_LIMITS = {
4
+ defaultMaxBytes: 32 * 1024,
5
+ minMaxBytes: 4 * 1024,
6
+ maxMaxBytes: 64 * 1024,
7
+ responseTtlMs: 5 * 60 * 1_000,
8
+ relayTimeoutMs: 15 * 1_000,
9
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.3.41",
3
+ "version": "0.3.43",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,15 +0,0 @@
1
- export type ChatAttachmentInput = {
2
- path: string;
3
- name: string;
4
- mimeType: string;
5
- previewData?: string;
6
- };
7
- export declare function materializeRemoteChatAttachments(input: {
8
- text: string;
9
- attachments?: ChatAttachmentInput[];
10
- workDir: string;
11
- }): Promise<{
12
- text: string;
13
- attachments?: ChatAttachmentInput[];
14
- localized: boolean;
15
- }>;
@@ -1 +0,0 @@
1
- import f from"node:crypto";import i from"node:fs";import o from"node:path";const l=Number(process.env.SHENNIAN_REMOTE_ATTACHMENT_MAX_BYTES||50*1024*1024);function m(t){return o.basename(t||"attachment").normalize("NFKC").replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").replace(/[\r\n\t]+/g," ").replace(/\s+/g," ").replace(/_+/g,"_").replace(/^[ ._]+|[ ._]+$/g,"")||"attachment"}function u(t,a,e){const r=m(a),n=o.extname(r),c=n?r.slice(0,-n.length):r,s=o.join(t,r);return i.existsSync(s)?o.join(t,`${c}-${e.slice(0,8)}${n}`):s}function p(t){return/^https?:\/\//i.test(t)}async function d(t,a){if(!p(t.path))return t;const e=await fetch(t.path);if(!e.ok||Number(e.headers.get("content-length")||0)>l)return t;const n=Buffer.from(await e.arrayBuffer());if(!n.byteLength||n.byteLength>l)return t;const c=f.createHash("sha256").update(n).digest("hex"),s=o.join(a,".uploads");i.mkdirSync(s,{recursive:!0});const h=u(s,t.name,c);return i.existsSync(h)||i.writeFileSync(h,n),{...t,path:h,mimeType:t.mimeType||e.headers.get("content-type")||"application/octet-stream"}}function g(t,a,e){return a.path===e.path||!a.path||!e.path?t:t.split(a.path).join(e.path)}async function _(t){if(!t.attachments?.length)return{text:t.text,attachments:t.attachments,localized:!1};const a=[];let e=t.text,r=!1;for(const n of t.attachments){const c=await d(n,t.workDir).catch(()=>n);c.path!==n.path&&p(n.path)&&(r=!0),e=g(e,n,c),a.push(c)}return{text:e,attachments:a,localized:r}}export{_ as materializeRemoteChatAttachments};
@@ -1 +0,0 @@
1
- export declare function buildToolArgsSummary(value: unknown): unknown;
@@ -1 +0,0 @@
1
- const o=240;function c(t){if(t==null)return;if(!t||typeof t!="object"||Array.isArray(t))return r(t);const n={};for(const[i,e]of Object.entries(t))if(!(e==null||e==="")&&(n[i]=r(e),Object.keys(n).length===2))break;return Object.keys(n).length?n:void 0}function r(t){if(typeof t=="string")return s(t)?"[SYSTEM CONTEXT REDACTED]":t.replace(/\s+/g," ").trim().slice(0,240);const n=JSON.stringify(t);return String(n??t).replace(/\s+/g," ").slice(0,240)}function s(t){const n=t.trim();return/^<environment_context>[\s\S]*<\/environment_context>$/i.test(n)||/^# AGENTS\.md instructions for [^\n]+[\s\S]*<INSTRUCTIONS>[\s\S]*<\/INSTRUCTIONS>/i.test(n)||/^<turn_aborted>[\s\S]*<\/turn_aborted>$/i.test(n)}export{c as buildToolArgsSummary};