termdock 1.4.346 → 1.4.347
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/agent/collaborationStore.js +40 -12
- package/dist/server/agent/integrationServer.js +57 -31
- package/dist/server/agent/integrationSessions.js +36 -8
- package/dist/server/agent/integrationStore.js +6 -5
- package/dist/server/routes/terminal.js +2 -2
- package/docs/integrations.md +11 -0
- package/package.json +1 -1
- package/runtime-manifest.json +2 -2
|
@@ -467,6 +467,12 @@ export class CollaborationStore {
|
|
|
467
467
|
const group = this.getGroup(groupId);
|
|
468
468
|
if (!group || group.deleted || !context || typeof context !== 'object')
|
|
469
469
|
return;
|
|
470
|
+
if (this.applyContext(group, context))
|
|
471
|
+
this.persist();
|
|
472
|
+
}
|
|
473
|
+
applyContext(group, context) {
|
|
474
|
+
if (group.deleted)
|
|
475
|
+
return false;
|
|
470
476
|
let changed = false;
|
|
471
477
|
const rules = context.instructions;
|
|
472
478
|
if (rules && validRevision(rules) && typeof rules.text === 'string' && Buffer.byteLength(rules.text) <= 8192
|
|
@@ -488,8 +494,7 @@ export class CollaborationStore {
|
|
|
488
494
|
delete group.roles[id];
|
|
489
495
|
changed = true;
|
|
490
496
|
}
|
|
491
|
-
|
|
492
|
-
this.persist();
|
|
497
|
+
return changed;
|
|
493
498
|
}
|
|
494
499
|
removeSession(sessionId) {
|
|
495
500
|
const count = this.document.groups.filter(group => group.sessionIds.includes(sessionId)).length;
|
|
@@ -712,12 +717,17 @@ export class CollaborationStore {
|
|
|
712
717
|
if (existing && (!existing.federated || (existing.deleted && (!group.deleted || existing.updatedAt >= group.updatedAt))
|
|
713
718
|
|| (!group.deleted && existing.updatedAt > group.updatedAt)))
|
|
714
719
|
return;
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
720
|
+
// Directory refreshes repeat the same group every few seconds. Compare
|
|
721
|
+
// content as well as the timestamp: equal timestamps can carry real edits.
|
|
722
|
+
if (existing && stableJson(existing) === stableJson(group))
|
|
723
|
+
return;
|
|
724
|
+
const incoming = structuredClone(group.deleted && existing && !existing.deleted
|
|
725
|
+
? { ...group, updatedAt: Math.max(group.updatedAt, existing.updatedAt + 1) } : group);
|
|
726
|
+
if (existing)
|
|
727
|
+
this.applyContext(incoming, this.context(group.id));
|
|
728
|
+
if (existing && stableJson(existing) === stableJson(incoming))
|
|
729
|
+
return;
|
|
730
|
+
this.document.groups = [...this.document.groups.filter((item) => item.id !== group.id), incoming];
|
|
721
731
|
if (group.deleted)
|
|
722
732
|
this.document.messages = this.document.messages.filter((item) => item.groupId !== group.id);
|
|
723
733
|
this.persist();
|
|
@@ -861,16 +871,34 @@ export class CollaborationStore {
|
|
|
861
871
|
const message = this.getMessage(id);
|
|
862
872
|
if (!message)
|
|
863
873
|
throw new CollaborationError('MESSAGE_NOT_FOUND', 'Message not found or no longer retained', 404);
|
|
864
|
-
|
|
865
|
-
|
|
874
|
+
return this.messageReceipt(message, this.document.messages.filter(reply => reply.replyTo === id), raw);
|
|
875
|
+
}
|
|
876
|
+
/** Reconcile message events in one pass without rescanning the complete
|
|
877
|
+
* history for every receipt or formatting snapshots which events omit. */
|
|
878
|
+
snapshotMessagesWithReceipts(groups) {
|
|
879
|
+
const replies = new Map();
|
|
880
|
+
for (const message of this.document.messages)
|
|
881
|
+
if (message.replyTo) {
|
|
882
|
+
const list = replies.get(message.replyTo) ?? [];
|
|
883
|
+
list.push(message);
|
|
884
|
+
replies.set(message.replyTo, list);
|
|
885
|
+
}
|
|
886
|
+
return structuredClone(this.document.messages.filter(message => groups.has(message.groupId)).map(message => {
|
|
887
|
+
const { snapshot: _, ...receipt } = this.messageReceipt(message, replies.get(message.id) ?? [], false, false);
|
|
888
|
+
return { message, receipt };
|
|
889
|
+
}));
|
|
890
|
+
}
|
|
891
|
+
messageReceipt(message, candidates, raw, includeSnapshot = true) {
|
|
892
|
+
const id = message.id;
|
|
893
|
+
const replies = candidates.filter(reply => (message.toPrincipalId ? reply.integrationOrigin?.integrationId === message.toPrincipalId : reply.fromSessionId === message.toSessionId)
|
|
866
894
|
&& (message.integrationOrigin ? reply.toPrincipalId === message.integrationOrigin.integrationId : reply.toSessionId === message.fromSessionId));
|
|
867
895
|
const diagnostic = this.diagnostic(id);
|
|
868
|
-
const facts = terminalMessage(message, raw);
|
|
896
|
+
const facts = terminalMessage(includeSnapshot ? message : { ...message, snapshot: null }, raw);
|
|
869
897
|
return { message_id: id, thread_id: message.threadId, status: facts.status, queued_at: message.status === 'stored' ? null : message.createdAt, stored_at: message.storedAt ?? null,
|
|
870
898
|
from_principal_id: message.integrationOrigin?.integrationId ?? null, to_principal_id: message.toPrincipalId ?? null,
|
|
871
899
|
delivered_at: facts.deliveredAt, expires_at: message.expiresAt ?? null,
|
|
872
900
|
failure_reason: message.failureReason ?? null, delivery_semantics: message.status === 'stored' ? 'durable_storage' : facts.deliverySource ?? (facts.deliveredAt === null ? 'not_delivered' : 'legacy_or_unspecified'),
|
|
873
|
-
snapshot: message.snapshot ? (raw ? message.snapshot : plainCollaborationSnapshot(message.snapshot)) : null,
|
|
901
|
+
snapshot: includeSnapshot && message.snapshot ? (raw ? message.snapshot : plainCollaborationSnapshot(message.snapshot)) : null,
|
|
874
902
|
delivery: { status: facts.status,
|
|
875
903
|
stage: message.status === 'stored' ? 'stored' : facts.deliveredAt !== null ? 'terminal_written' : diagnostic?.route_state === 'identity-mismatch' || diagnostic?.last_error === 'TMUX_PANE_CHANGED' ? 'blocked' : diagnostic?.remote_received_at ? 'remote_received' : 'queued',
|
|
876
904
|
...(!diagnostic?.route_diagnostics && diagnostic?.last_error === 'TMUX_PANE_CHANGED' ? { requires_confirmation: true } : {}),
|
|
@@ -29,6 +29,8 @@ export class IntegrationRuntime {
|
|
|
29
29
|
streams = new Set();
|
|
30
30
|
sent = new Map();
|
|
31
31
|
syncing = false;
|
|
32
|
+
dirtySources = new Set(['messages', 'tasks', 'sessions']);
|
|
33
|
+
syncedGroups = '';
|
|
32
34
|
retry;
|
|
33
35
|
releaseBatchFile;
|
|
34
36
|
releaseBatchOperations = [];
|
|
@@ -57,14 +59,17 @@ export class IntegrationRuntime {
|
|
|
57
59
|
throw error;
|
|
58
60
|
}
|
|
59
61
|
this.stops.push(options.messages.registerRuntimeGuard(id => this.sessions.assertRuntimeMessage(id)));
|
|
60
|
-
const changed = () =>
|
|
61
|
-
this.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
const changed = (source) => () => {
|
|
63
|
+
this.dirtySources.add(source);
|
|
64
|
+
try {
|
|
65
|
+
this.sync();
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
this.failStreams(error);
|
|
69
|
+
this.scheduleRetry();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
this.stops.push(options.messages.subscribe(changed('messages')), options.taskStore.subscribe(changed('tasks')), this.sessions.subscribe(changed('sessions')));
|
|
68
73
|
const app = express();
|
|
69
74
|
app.use(express.json({ limit: '2mb' }));
|
|
70
75
|
app.use((req, res, next) => {
|
|
@@ -461,11 +466,25 @@ export class IntegrationRuntime {
|
|
|
461
466
|
if (this.syncing)
|
|
462
467
|
return;
|
|
463
468
|
this.syncing = true;
|
|
469
|
+
let sources = new Set();
|
|
464
470
|
try {
|
|
465
471
|
const groups = new Set(this.store.policies().filter(p => !p.revoked).map(p => p.groupId)), updates = [];
|
|
466
|
-
|
|
472
|
+
const groupVersion = JSON.stringify([...groups].sort());
|
|
473
|
+
// Newly granted groups need a complete source reconciliation, including
|
|
474
|
+
// retained facts that predate the grant. Never advance dirty state until
|
|
475
|
+
// the durable event append succeeds.
|
|
476
|
+
if (groupVersion !== this.syncedGroups)
|
|
477
|
+
for (const source of ['messages', 'tasks', 'sessions'])
|
|
478
|
+
this.dirtySources.add(source);
|
|
479
|
+
sources = this.dirtySources;
|
|
480
|
+
this.dirtySources = new Set();
|
|
481
|
+
if (!sources.size)
|
|
482
|
+
return;
|
|
483
|
+
if (!groups.size) {
|
|
484
|
+
this.syncedGroups = groupVersion;
|
|
467
485
|
return;
|
|
468
|
-
|
|
486
|
+
}
|
|
487
|
+
for (const task of sources.has('tasks') ? this.options.taskStore.snapshot() : [])
|
|
469
488
|
if (groups.has(task.groupId)) {
|
|
470
489
|
for (const event of task.events)
|
|
471
490
|
updates.push({ sourceKey: `task:${task.id}:${event.id}`, sourceVersion: '1', eventId: event.id, event: {
|
|
@@ -485,31 +504,30 @@ export class IntegrationRuntime {
|
|
|
485
504
|
task_id: task.id, attempt_id: delivery.attemptId, message_id: delivery.messageId, source: 'system', payload: { delivery }
|
|
486
505
|
} });
|
|
487
506
|
}
|
|
488
|
-
for (const message of this.options.messages.
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
for (const session of this.sessions.snapshot())
|
|
507
|
+
for (const { message, receipt } of sources.has('messages') ? this.options.messages.snapshotMessagesWithReceipts(groups) : []) {
|
|
508
|
+
const link = message.metadata?.termdockTask;
|
|
509
|
+
const context = { group_id: message.groupId, task_id: link?.taskId ?? null, attempt_id: link?.attemptId ?? null,
|
|
510
|
+
reply_to_event_id: link?.replyToEventId ?? null, message_id: message.id, source: message.integrationOrigin ? 'integration' : message.fromSessionId ? 'member' : 'system',
|
|
511
|
+
actor: message.integrationOrigin ? { integration_id: message.integrationOrigin.integrationId } : message.fromSessionId ? { session_id: message.fromSessionId } : null,
|
|
512
|
+
external_actor: message.integrationOrigin?.externalActor ?? null };
|
|
513
|
+
updates.push({ sourceKey: `message:${message.id}`, sourceVersion: '1', event: { ...context,
|
|
514
|
+
kind: message.kind === 'reply' ? 'message.reply' : 'message.queued', created_at: message.createdAt,
|
|
515
|
+
payload: { message_id: message.id, thread_id: message.threadId, reply_to: message.replyTo ?? null,
|
|
516
|
+
from_session_id: message.fromSessionId, to_session_id: message.toPrincipalId ? null : message.toSessionId,
|
|
517
|
+
to_principal_id: message.toPrincipalId ?? null, integration_origin: message.integrationOrigin ?? null,
|
|
518
|
+
response_kind: message.responseKind ?? null, stored_at: message.storedAt ?? null,
|
|
519
|
+
content: message.content, metadata: message.metadata ?? null } } });
|
|
520
|
+
updates.push({ sourceKey: `message-status:${message.id}`, sourceVersion: JSON.stringify(receipt), event: {
|
|
521
|
+
...context, kind: 'message.delivery', created_at: message.deliveredAt ?? message.createdAt, payload: { receipt }
|
|
522
|
+
} });
|
|
523
|
+
}
|
|
524
|
+
const sessions = sources.has('sessions') ? this.sessions.snapshot() : [];
|
|
525
|
+
for (const session of sessions)
|
|
508
526
|
if (groups.has(session.group_id))
|
|
509
527
|
updates.push({ sourceKey: `session:${session.session_id}`, sourceVersion: JSON.stringify(publicIntegrationSession(session)), event: {
|
|
510
528
|
group_id: session.group_id, kind: `session.${session.state}`, created_at: session.updated_at, source: 'system', actor: { integration_id: session.principal_id }, payload: { session: publicIntegrationSession(session) }
|
|
511
529
|
} });
|
|
512
|
-
for (const session of
|
|
530
|
+
for (const session of sessions)
|
|
513
531
|
if (groups.has(session.group_id) && session.name_updated_at)
|
|
514
532
|
updates.push({
|
|
515
533
|
sourceKey: `session-name:${session.session_id}`, sourceVersion: JSON.stringify([session.name, session.name_updated_at, session.name_generation]), event: {
|
|
@@ -519,9 +537,17 @@ export class IntegrationRuntime {
|
|
|
519
537
|
});
|
|
520
538
|
updates.sort((a, b) => a.event.created_at - b.event.created_at);
|
|
521
539
|
this.store.append(updates);
|
|
540
|
+
this.syncedGroups = groupVersion;
|
|
541
|
+
}
|
|
542
|
+
catch (error) {
|
|
543
|
+
for (const source of sources)
|
|
544
|
+
this.dirtySources.add(source);
|
|
545
|
+
throw error;
|
|
522
546
|
}
|
|
523
547
|
finally {
|
|
524
548
|
this.syncing = false;
|
|
549
|
+
if (this.dirtySources.size)
|
|
550
|
+
this.scheduleRetry();
|
|
525
551
|
}
|
|
526
552
|
}
|
|
527
553
|
failStreams(error) {
|
|
@@ -68,6 +68,8 @@ export class IntegrationSessions {
|
|
|
68
68
|
stopDeliveryGuard;
|
|
69
69
|
observing = false;
|
|
70
70
|
observeAgain = false;
|
|
71
|
+
observationTimer;
|
|
72
|
+
stopped = false;
|
|
71
73
|
constructor(file, adapter, bindingTimeout = 120000) {
|
|
72
74
|
this.file = file;
|
|
73
75
|
this.adapter = adapter;
|
|
@@ -94,7 +96,22 @@ export class IntegrationSessions {
|
|
|
94
96
|
if (record.startup_input && record.startup_input.state !== 'observed')
|
|
95
97
|
record.startup_input.matched_since = null;
|
|
96
98
|
this.stopDeliveryGuard = adapter.registerDeliveryGuard?.(id => this.deliveryReadiness(id));
|
|
97
|
-
this.stopObservation = adapter.subscribe(() =>
|
|
99
|
+
this.stopObservation = adapter.subscribe(() => this.scheduleObservation());
|
|
100
|
+
}
|
|
101
|
+
scheduleObservation() {
|
|
102
|
+
if (this.stopped)
|
|
103
|
+
return;
|
|
104
|
+
if (this.observing) {
|
|
105
|
+
this.observeAgain = true;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (this.observationTimer)
|
|
109
|
+
return;
|
|
110
|
+
this.observationTimer = setTimeout(() => {
|
|
111
|
+
this.observationTimer = undefined;
|
|
112
|
+
void this.refreshAll().catch(() => { });
|
|
113
|
+
}, 250);
|
|
114
|
+
this.observationTimer.unref();
|
|
98
115
|
}
|
|
99
116
|
scheduleRefresh(record) {
|
|
100
117
|
for (const deadline of new Set([record.deadline, ...(record.startup_input ? [record.startup_input.deadline] : [])])) {
|
|
@@ -974,18 +991,29 @@ export class IntegrationSessions {
|
|
|
974
991
|
this.observeAgain = true;
|
|
975
992
|
return;
|
|
976
993
|
}
|
|
994
|
+
if (this.observationTimer) {
|
|
995
|
+
clearTimeout(this.observationTimer);
|
|
996
|
+
this.observationTimer = undefined;
|
|
997
|
+
}
|
|
977
998
|
this.observing = true;
|
|
999
|
+
this.observeAgain = false;
|
|
978
1000
|
try {
|
|
979
|
-
|
|
980
|
-
this.
|
|
981
|
-
for (const record of this.snapshot())
|
|
982
|
-
await this.refresh(record.session_id);
|
|
983
|
-
} while (this.observeAgain);
|
|
1001
|
+
for (const record of this.snapshot())
|
|
1002
|
+
await this.refresh(record.session_id);
|
|
984
1003
|
}
|
|
985
1004
|
finally {
|
|
986
1005
|
this.observing = false;
|
|
1006
|
+
if (this.observeAgain)
|
|
1007
|
+
this.scheduleObservation();
|
|
987
1008
|
}
|
|
988
1009
|
}
|
|
989
|
-
close() {
|
|
990
|
-
|
|
1010
|
+
close() {
|
|
1011
|
+
this.stopped = true;
|
|
1012
|
+
this.stopObservation();
|
|
1013
|
+
this.stopDeliveryGuard?.();
|
|
1014
|
+
if (this.observationTimer)
|
|
1015
|
+
clearTimeout(this.observationTimer);
|
|
1016
|
+
for (const timer of this.timers)
|
|
1017
|
+
clearTimeout(timer);
|
|
1018
|
+
}
|
|
991
1019
|
}
|
|
@@ -152,18 +152,19 @@ export class IntegrationStore {
|
|
|
152
152
|
integrationError('INTEGRATION_PERMISSION_DENIED', 'Operation is outside the integration grant', 403);
|
|
153
153
|
}
|
|
154
154
|
append(updates) {
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
// Most reconciliations repeat already-journaled facts. Do not copy the
|
|
156
|
+
// retained event history until the first actual source-version change.
|
|
157
|
+
let next;
|
|
157
158
|
for (const update of updates) {
|
|
158
|
-
if (next.sources[update.sourceKey] === update.sourceVersion)
|
|
159
|
+
if ((next ?? this.doc).sources[update.sourceKey] === update.sourceVersion)
|
|
159
160
|
continue;
|
|
161
|
+
next ??= structuredClone(this.doc);
|
|
160
162
|
const group = update.event.group_id, sequence = (next.sequences[group] ?? 0) + 1;
|
|
161
163
|
next.events.push({ ...update.event, event_id: update.eventId ?? randomUUID(), sequence, cursor: this.cursor(group, sequence) });
|
|
162
164
|
next.sequences[group] = sequence;
|
|
163
165
|
next.sources[update.sourceKey] = update.sourceVersion;
|
|
164
|
-
changed = true;
|
|
165
166
|
}
|
|
166
|
-
if (!
|
|
167
|
+
if (!next)
|
|
167
168
|
return;
|
|
168
169
|
const counts = new Map();
|
|
169
170
|
next.events = next.events.reverse().filter(event => {
|
|
@@ -1765,7 +1765,7 @@ async function inspectCollaborationTmux(binding, requestedPane, allowPlainPane =
|
|
|
1765
1765
|
// pane so a plain shell member is drivable (run/capture are shell ops).
|
|
1766
1766
|
return selectDrivePane(binding, candidates, layout.activePaneId);
|
|
1767
1767
|
}
|
|
1768
|
-
return { ...selectCollaborationPane(binding, candidates), candidates };
|
|
1768
|
+
return { ...selectCollaborationPane(binding, candidates), candidates, layout };
|
|
1769
1769
|
}
|
|
1770
1770
|
function routeOwnershipConfirmed(frontendSessionId, binding, panes) {
|
|
1771
1771
|
const record = globalSessionState.sessions.find(candidate => candidate.sessionId === frontendSessionId);
|
|
@@ -2350,7 +2350,7 @@ export const integrationSessionAdapter = {
|
|
|
2350
2350
|
if (inspected.state !== 'ready' || !inspected.pane)
|
|
2351
2351
|
integrationError('SESSION_IDENTITY_MISMATCH', 'Owned tmux pane cannot be uniquely verified', 409);
|
|
2352
2352
|
const pane = inspected.pane;
|
|
2353
|
-
const layout =
|
|
2353
|
+
const layout = inspected.layout;
|
|
2354
2354
|
if (layout.windows.flatMap(window => window.panes).length !== 1 || layout.activePaneId !== pane.paneId)
|
|
2355
2355
|
integrationError('SESSION_IDENTITY_MISMATCH', 'Integration native binding requires the single owned pane', 409);
|
|
2356
2356
|
const backend = terminal.backendSessionId ? terminalSessions.get(terminal.backendSessionId) : null;
|
package/docs/integrations.md
CHANGED
|
@@ -1383,3 +1383,14 @@ wrapper 负责实际设置 Agent 自己的数据目录、准备 hooks/参数,
|
|
|
1383
1383
|
用 stdin 文件给启动命令附带业务 prompt 是接入方的 bootstrap:TD 不为其中业务副作用提供消息幂等或执行确认;无 TD message 历史也不等于没有业务执行。冷启动与精确 resume 应分开处理,恢复时不得重放初始业务文件。host 收取 final/hook receipt 是 host 侧证据,不伪造成 TD 明确 reply。仍只粘贴一次、等待 50ms、写一次 CR。
|
|
1384
1384
|
|
|
1385
1385
|
回归包含确定性交错的旧读盘覆盖/串行写入/删除不复活、同 pane shell 初始化等待与超时/换 pane/已有 Agent 拒绝、CLI 对旧服务拒绝,以及 Linux 真实 tmux/内核 flock 的独立目录核验。私有 TraeX/DevBox 的启动兼容仍需接入方维护窗实机验证;维护机合成测试不冒充该项通过。
|
|
1386
|
+
|
|
1387
|
+
## 1.4.347:无变化同步与后台观察的开销
|
|
1388
|
+
|
|
1389
|
+
这是服务运行时优化,不增加接口、权限或配置。需要更新实际运行服务到 **1.4.347**;仅安装新 CLI 不会更新已运行进程。
|
|
1390
|
+
|
|
1391
|
+
- 跨服务目录重复合并相同群内容时不写盘、不通知事件同步。比较包含内容而非仅时间戳;同时间戳的真实变化仍处理。合并独立群规/角色版本后如有效内容未变也不写盘;有效群更新与保留的新上下文一次持久化。
|
|
1392
|
+
- 空事件追加及 source/version 全重复的追加在克隆事件历史之前返回。真实更新继续持久提交,失败保留可重试源版本,不改变事件 ID、顺序、ACK 或保留策略。
|
|
1393
|
+
- 事件同步按消息、任务、会话三个变化来源分别扫描;会话通知不再复制消息历史和生成全部消息回执。新授权组及服务重启仍对保留事实完整对账;失败来源继续重试。消息变化时一次建立回复索引,避免每条回执重扫历史;回执仍核验双方身份,并省去事件中不包含的终端快照格式化。这是按来源增量同步,真实变化仍会扫描该来源的保留记录。
|
|
1394
|
+
- 终端输出触发的后台会话观察按 250ms 窗口合并;一轮观察期间的新通知安排到后续一轮,不连续抢占。显式 `session get`、生命周期操作及投递前核验保留原路径,不以此窗口判断 Agent 可输入;不延迟业务输入来猜测应用状态。同一次 integration 观察复用其新鲜 tmux layout,下一次观察重新读取,不把缓存用作身份写入凭据。
|
|
1395
|
+
|
|
1396
|
+
无删除消息/事件、缩短历史、停用消费者、额外 Enter 或正文重发。`pty_written` 仍只代表终端写入调用完成,不代表应用提交或任务完成。回归包含约 26 MB journal 的空/重复追加零克隆、相同群同步零写入、真实变化/持久化失败重试、授权后历史补收、通知洪峰合并、换 pane/原生身份冲突拒绝。生产 CPU、事件循环延迟和 health 改善须在实际维护窗升级后对比采样,不能用隔离基准代替实机验收。
|
package/package.json
CHANGED
package/runtime-manifest.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"packageName": "termdock",
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.347",
|
|
5
5
|
"runtimeProtocolVersion": 1,
|
|
6
6
|
"minimumDesktopVersion": "1.4.46",
|
|
7
7
|
"nodeMajor": 22,
|
|
8
8
|
"dependencyHash": "sha256-ObG1OGq7e7fMFQmYoPxPp3jRodEusYHulVNa5mT1aW8=",
|
|
9
|
-
"serverBundleHash": "sha256-
|
|
9
|
+
"serverBundleHash": "sha256-FXUdT0eV3/+rgHBnc0Jg9dn2sAb4hcLY83xScGapcKQ=",
|
|
10
10
|
"clientBundleHash": "sha256-kn3Tg+BTj+H4rZe0g9OE5JUEQNsBGiUSGew8Epm5A38=",
|
|
11
11
|
"entrypoint": "dist/server/cli.js",
|
|
12
12
|
"clientEntrypoint": "dist/client/index.html"
|