tmux-ide 2.9.10 → 2.9.12
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/bin/cli.js +4321 -2596
- package/package.json +1 -1
- package/packages/contracts/src/index.ts +4 -0
- package/packages/contracts/src/pane-stream.ts +72 -0
- package/packages/contracts/src/terminal-delivery-binary-chunk.ts +55 -0
- package/packages/contracts/src/terminal-delivery.ts +17 -2
- package/packages/contracts/src/terminal-graphics.ts +68 -0
- package/packages/contracts/src/terminal-projection.ts +153 -0
- package/packages/core/src/fixtures/terminal-projection-vectors.json +585 -0
- package/packages/core/src/index.ts +2 -0
- package/packages/core/src/terminal-delivery-binary.ts +160 -0
- package/packages/core/src/terminal-delivery.ts +371 -16
- package/packages/core/src/terminal-projection-vectors.mjs +82 -0
- package/packages/core/src/terminal-projection.ts +476 -0
- package/packages/core/src/terminal-replica-hash-cache.ts +8 -1
- package/packages/core/src/terminal-replica.ts +1 -1
- package/packages/daemon/dist/lib/daemon-embed.js +39 -3
- package/packages/daemon/dist/lib/embedded-tmux-server-owners.js +1 -0
- package/packages/daemon/dist/lib/tmux-server-owner.js +1 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +2 -2
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +2 -2
- package/packages/daemon/dist/terminal/graphics/local-image-owner.js +330 -0
- package/packages/daemon/dist/terminal/graphics/local-image-store.js +255 -0
- package/packages/daemon/dist/terminal/mirror/mirror-service.js +12 -2
- package/packages/daemon/dist/terminal/mirror/session-channel.js +46 -23
- package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +190 -19
- package/packages/daemon/dist/terminal/pane-stream/runtime.js +1 -0
- package/packages/daemon/dist/terminal/session-runtime/registry.js +19 -6
- package/packages/daemon/dist/terminal/session-runtime/terminal-delivery-hub.js +146 -16
- package/packages/daemon/dist/terminal/session-runtime/terminal-projection-delivery.js +32 -0
- package/packages/daemon/dist/terminal/session-runtime/terminal-replica-interpreter.js +95 -36
- package/packages/daemon/dist/terminal/session-runtime/terminal-replica-owner.js +5 -0
- package/packages/daemon/dist/terminal/session-runtime/transport-binding.js +2 -2
- package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +4 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-generation-starter.js +16 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-agent-navigation.js +13 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +12 -4
- package/packages/daemon/dist/tui/mirror/runtime/application-retained-agent.js +18 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +90 -5
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +13 -1
- package/packages/daemon/dist/tui/mirror/runtime/open-tui-generation-host.js +2 -2
- package/packages/daemon/dist/tui/mirror/runtime/renderer-probe.js +195 -0
- package/packages/daemon/dist/tui/mirror/runtime/runtime-layout-presentation.js +6 -3
- package/packages/daemon/dist/tui/mirror/runtime/select-terminal-pane.js +7 -2
- package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +6 -0
- package/packages/daemon/dist/tui/mirror/runtime/terminal-selection-waiter.js +36 -0
- package/packages/daemon/dist/tui/mirror/semantic-pane-render-source.js +4 -0
- package/packages/daemon/src/lib/daemon-embed.ts +38 -2
- package/packages/daemon/src/lib/embedded-tmux-server-owners.ts +3 -0
- package/packages/daemon/src/lib/tmux-server-owner.ts +3 -0
- package/packages/daemon/src/terminal/graphics/local-image-owner.ts +346 -0
- package/packages/daemon/src/terminal/graphics/local-image-store.ts +286 -0
- package/packages/daemon/src/terminal/mirror/mirror-service.ts +19 -2
- package/packages/daemon/src/terminal/mirror/session-channel.ts +57 -9
- package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +248 -16
- package/packages/daemon/src/terminal/pane-stream/runtime.ts +3 -0
- package/packages/daemon/src/terminal/session-runtime/registry.ts +25 -3
- package/packages/daemon/src/terminal/session-runtime/runtime-observability.ts +1 -1
- package/packages/daemon/src/terminal/session-runtime/terminal-delivery-hub.ts +216 -22
- package/packages/daemon/src/terminal/session-runtime/terminal-delivery-observation-identity.ts +7 -4
- package/packages/daemon/src/terminal/session-runtime/terminal-projection-delivery.ts +59 -0
- package/packages/daemon/src/terminal/session-runtime/terminal-replica-interpreter.ts +102 -44
- package/packages/daemon/src/terminal/session-runtime/terminal-replica-owner.ts +6 -0
- package/packages/daemon/src/terminal/session-runtime/transport-binding.ts +3 -0
- package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +6 -0
- package/packages/daemon/src/tui/mirror/runtime/application-generation-starter.ts +17 -1
- package/packages/daemon/src/tui/mirror/runtime/application-machine-agent-navigation.ts +15 -0
- package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +18 -4
- package/packages/daemon/src/tui/mirror/runtime/application-retained-agent.ts +29 -0
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +89 -6
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +20 -1
- package/packages/daemon/src/tui/mirror/runtime/open-tui-generation-host.ts +2 -2
- package/packages/daemon/src/tui/mirror/runtime/renderer-probe.ts +257 -0
- package/packages/daemon/src/tui/mirror/runtime/runtime-layout-presentation.ts +7 -4
- package/packages/daemon/src/tui/mirror/runtime/select-terminal-pane.ts +9 -1
- package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +15 -0
- package/packages/daemon/src/tui/mirror/runtime/terminal-selection-waiter.ts +36 -0
- package/packages/daemon/src/tui/mirror/semantic-pane-render-source.ts +4 -0
- package/packages/daemon-client/src/pane-stream-client.ts +92 -1
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { isOwnedTerminalReplicaRow } from "./terminal-replica-owned-row.ts";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
TERMINAL_DELIVERY_MAX_REPRESENTATION_BYTES,
|
|
5
|
+
TerminalProjectionSnapshotSchemaZ,
|
|
6
|
+
TerminalProjectionPatchSchemaZ,
|
|
7
|
+
TerminalProjectionPayloadSchemaZ,
|
|
8
|
+
TerminalDeferredHistorySchemaZ,
|
|
9
|
+
TerminalProjectionEnvelopeSchemaZ,
|
|
10
|
+
TerminalProjectionReceiptSchemaZ,
|
|
11
|
+
type TerminalReplicaSnapshot,
|
|
12
|
+
type TerminalProjectionSnapshot,
|
|
13
|
+
type TerminalProjectionPatch,
|
|
14
|
+
type TerminalProjectionPayload,
|
|
15
|
+
type TerminalDeferredHistory,
|
|
16
|
+
type TerminalProjectionEnvelope,
|
|
17
|
+
type TerminalProjectionReceipt,
|
|
18
|
+
} from "@tmux-ide/contracts";
|
|
19
|
+
import {
|
|
20
|
+
hashTerminalReplicaRows,
|
|
21
|
+
hashTerminalReplicaTombstone,
|
|
22
|
+
freezeTerminalReplicaRow,
|
|
23
|
+
terminalReplicaRowsEqual,
|
|
24
|
+
} from "./terminal-replica.ts";
|
|
25
|
+
import { hashCanonicalTerminalValue } from "./terminal-replica-hash-cache.ts";
|
|
26
|
+
import {
|
|
27
|
+
BinaryTerminalWriter,
|
|
28
|
+
BinaryTerminalLimitError,
|
|
29
|
+
decodeBinaryTerminalTuple,
|
|
30
|
+
} from "./terminal-delivery-binary.ts";
|
|
31
|
+
import {
|
|
32
|
+
TerminalDeliveryStateTooLargeError,
|
|
33
|
+
writeTerminalProjectionRowCooperatively,
|
|
34
|
+
encodeTerminalProjectionMetadataTuple,
|
|
35
|
+
encodeTerminalProjectionViewportTuple,
|
|
36
|
+
decodeTerminalProjectionViewportTuple,
|
|
37
|
+
encodeTerminalProjectionPatchTuple,
|
|
38
|
+
decodeTerminalProjectionPatchTuple,
|
|
39
|
+
hashTerminalDeliveryRepresentation,
|
|
40
|
+
} from "./terminal-delivery.ts";
|
|
41
|
+
|
|
42
|
+
export function terminalHistoryCommitment(
|
|
43
|
+
snapshot: TerminalReplicaSnapshot,
|
|
44
|
+
): TerminalDeferredHistory {
|
|
45
|
+
return Object.freeze(
|
|
46
|
+
TerminalDeferredHistorySchemaZ.parse({
|
|
47
|
+
rows: snapshot.history.length,
|
|
48
|
+
digest: hashTerminalReplicaRows(snapshot.history),
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
export function projectAlternateSnapshot(
|
|
53
|
+
snapshot: TerminalReplicaSnapshot,
|
|
54
|
+
): TerminalProjectionSnapshot {
|
|
55
|
+
const { history: _history, ...viewport } = snapshot;
|
|
56
|
+
void _history;
|
|
57
|
+
const candidate = { ...viewport, deferredHistory: terminalHistoryCommitment(snapshot) };
|
|
58
|
+
if (snapshot.grid.every(isOwnedTerminalReplicaRow)) {
|
|
59
|
+
const metadata = projectionMetadata(candidate);
|
|
60
|
+
validateViewportShape(snapshot.grid, metadata);
|
|
61
|
+
return freezeProjection({ ...metadata, grid: snapshot.grid });
|
|
62
|
+
}
|
|
63
|
+
return freezeProjection(TerminalProjectionSnapshotSchemaZ.parse(candidate));
|
|
64
|
+
}
|
|
65
|
+
function freezeProjection(value: TerminalProjectionSnapshot): TerminalProjectionSnapshot {
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
...value,
|
|
68
|
+
grid: (Object.isFrozen(value.grid) && value.grid.every(isOwnedTerminalReplicaRow)
|
|
69
|
+
? value.grid
|
|
70
|
+
: Object.freeze(
|
|
71
|
+
value.grid.map((row) =>
|
|
72
|
+
isOwnedTerminalReplicaRow(row) ? row : freezeTerminalReplicaRow(row),
|
|
73
|
+
),
|
|
74
|
+
)) as unknown as TerminalProjectionSnapshot["grid"],
|
|
75
|
+
cursor: Object.freeze(value.cursor),
|
|
76
|
+
modes: Object.freeze(value.modes),
|
|
77
|
+
placements: Object.freeze(
|
|
78
|
+
value.placements.map((p) => Object.freeze(p)),
|
|
79
|
+
) as unknown as TerminalProjectionSnapshot["placements"],
|
|
80
|
+
bootstrap: Object.freeze(value.bootstrap),
|
|
81
|
+
deferredHistory: Object.freeze(value.deferredHistory),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const { grid: _projectionGrid, ...projectionMetadataShape } =
|
|
85
|
+
TerminalProjectionSnapshotSchemaZ.shape;
|
|
86
|
+
void _projectionGrid;
|
|
87
|
+
const ProjectionMetadataSchema = z.object(projectionMetadataShape).strict();
|
|
88
|
+
function projectionMetadata(
|
|
89
|
+
value: Omit<TerminalReplicaSnapshot, "history"> & { deferredHistory: TerminalDeferredHistory },
|
|
90
|
+
) {
|
|
91
|
+
const { grid: _grid, ...metadata } = value;
|
|
92
|
+
void _grid;
|
|
93
|
+
return ProjectionMetadataSchema.parse(metadata);
|
|
94
|
+
}
|
|
95
|
+
function validateViewportShape(
|
|
96
|
+
grid: TerminalProjectionSnapshot["grid"],
|
|
97
|
+
metadata: Omit<TerminalProjectionSnapshot, "grid">,
|
|
98
|
+
): void {
|
|
99
|
+
if (
|
|
100
|
+
grid.length !== metadata.rows ||
|
|
101
|
+
metadata.cols * metadata.rows > 1_000_000 ||
|
|
102
|
+
grid.some((row) => !row || row.cells.length !== metadata.cols) ||
|
|
103
|
+
metadata.cursor.x >= metadata.cols ||
|
|
104
|
+
metadata.cursor.y >= metadata.rows ||
|
|
105
|
+
metadata.placements.some(
|
|
106
|
+
(p) => p.row + p.rows > metadata.rows || p.column + p.columns > metadata.cols,
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
throw new TypeError("Invalid projection viewport");
|
|
110
|
+
}
|
|
111
|
+
export function hashTerminalProjection(value: TerminalProjectionSnapshot): string {
|
|
112
|
+
return hashCanonicalTerminalValue([
|
|
113
|
+
"terminal-projection-v1",
|
|
114
|
+
value.cols,
|
|
115
|
+
value.rows,
|
|
116
|
+
hashTerminalReplicaRows(value.grid),
|
|
117
|
+
value.cursor,
|
|
118
|
+
value.modes,
|
|
119
|
+
value.placements,
|
|
120
|
+
value.bootstrap,
|
|
121
|
+
[value.deferredHistory.rows, value.deferredHistory.digest],
|
|
122
|
+
]);
|
|
123
|
+
}
|
|
124
|
+
/** Checks the source root using its omitted-history commitment, not history materialization. */
|
|
125
|
+
export function hashSourceSnapshotFromProjection(value: TerminalProjectionSnapshot): string {
|
|
126
|
+
return hashCanonicalTerminalValue([
|
|
127
|
+
"terminal-replica-v1",
|
|
128
|
+
value.cols,
|
|
129
|
+
value.rows,
|
|
130
|
+
hashTerminalReplicaRows(value.grid),
|
|
131
|
+
value.deferredHistory.digest,
|
|
132
|
+
value.cursor,
|
|
133
|
+
value.modes,
|
|
134
|
+
value.placements,
|
|
135
|
+
value.bootstrap,
|
|
136
|
+
]);
|
|
137
|
+
}
|
|
138
|
+
function projectionEncodingError(error: unknown): never {
|
|
139
|
+
if (error instanceof BinaryTerminalLimitError)
|
|
140
|
+
throw new TerminalDeliveryStateTooLargeError(TERMINAL_DELIVERY_MAX_REPRESENTATION_BYTES + 1);
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
export function encodeTerminalProjection(input: TerminalProjectionPayload): Uint8Array {
|
|
144
|
+
try {
|
|
145
|
+
return encodeProjection(input);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
return projectionEncodingError(error);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function encodeProjection(input: TerminalProjectionPayload): Uint8Array {
|
|
151
|
+
const value = TerminalProjectionPayloadSchemaZ.parse(input);
|
|
152
|
+
const writer = new BinaryTerminalWriter();
|
|
153
|
+
writer.header(
|
|
154
|
+
value.frame,
|
|
155
|
+
value.revision,
|
|
156
|
+
value.frame === "seed" ? undefined : value.baseRevision,
|
|
157
|
+
"TCP1",
|
|
158
|
+
);
|
|
159
|
+
if (value.frame === "tombstone") writer.value(value.tombstone.reason);
|
|
160
|
+
else {
|
|
161
|
+
const data = value.frame === "seed" ? value.projection : value.patch;
|
|
162
|
+
const tuple =
|
|
163
|
+
value.frame === "seed"
|
|
164
|
+
? encodeTerminalProjectionViewportTuple(value.projection)
|
|
165
|
+
: encodeTerminalProjectionPatchTuple(value.patch);
|
|
166
|
+
writer.value([...tuple, [data.deferredHistory.rows, data.deferredHistory.digest]]);
|
|
167
|
+
}
|
|
168
|
+
return writer.finish();
|
|
169
|
+
}
|
|
170
|
+
export function decodeTerminalProjection(bytes: Uint8Array): TerminalProjectionPayload {
|
|
171
|
+
const decoded = decodeBinaryTerminalTuple(bytes, "TCP1");
|
|
172
|
+
if (decoded.frame === "tombstone")
|
|
173
|
+
return TerminalProjectionPayloadSchemaZ.parse({
|
|
174
|
+
frame: decoded.frame,
|
|
175
|
+
baseRevision: decoded.baseRevision,
|
|
176
|
+
revision: decoded.revision,
|
|
177
|
+
tombstone: { reason: decoded.value },
|
|
178
|
+
});
|
|
179
|
+
const tuple = decoded.value;
|
|
180
|
+
const expected = decoded.frame === "seed" ? 8 : 7;
|
|
181
|
+
if (!Array.isArray(tuple) || tuple.length !== expected)
|
|
182
|
+
throw new TypeError("Invalid projection tuple");
|
|
183
|
+
const deferred = tuple[expected - 1];
|
|
184
|
+
if (!Array.isArray(deferred) || deferred.length !== 2)
|
|
185
|
+
throw new TypeError("Invalid deferred history tuple");
|
|
186
|
+
const deferredHistory = TerminalDeferredHistorySchemaZ.parse({
|
|
187
|
+
rows: deferred[0],
|
|
188
|
+
digest: deferred[1],
|
|
189
|
+
});
|
|
190
|
+
if (decoded.frame === "seed")
|
|
191
|
+
return {
|
|
192
|
+
frame: "seed",
|
|
193
|
+
revision: decoded.revision,
|
|
194
|
+
projection: freezeProjection(
|
|
195
|
+
TerminalProjectionSnapshotSchemaZ.parse({
|
|
196
|
+
...decodeTerminalProjectionViewportTuple(tuple.slice(0, -1)),
|
|
197
|
+
deferredHistory,
|
|
198
|
+
}),
|
|
199
|
+
),
|
|
200
|
+
};
|
|
201
|
+
return TerminalProjectionPayloadSchemaZ.parse({
|
|
202
|
+
frame: "patch",
|
|
203
|
+
baseRevision: decoded.baseRevision,
|
|
204
|
+
revision: decoded.revision,
|
|
205
|
+
patch: { ...decodeTerminalProjectionPatchTuple(tuple.slice(0, -1)), deferredHistory },
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
export function applyTerminalProjectionPatch(
|
|
209
|
+
current: TerminalProjectionSnapshot,
|
|
210
|
+
input: TerminalProjectionPatch,
|
|
211
|
+
): TerminalProjectionSnapshot {
|
|
212
|
+
const patch = TerminalProjectionPatchSchemaZ.parse(input);
|
|
213
|
+
const cols = patch.dimensions?.cols ?? current.cols,
|
|
214
|
+
rows = patch.dimensions?.rows ?? current.rows;
|
|
215
|
+
// A resize requires every visible row, avoiding invented blank cells or implicit history.
|
|
216
|
+
if (cols !== current.cols || rows !== current.rows) {
|
|
217
|
+
if (patch.rows.length !== rows || new Set(patch.rows.map((r) => r.index)).size !== rows)
|
|
218
|
+
throw new TypeError("Projection resize requires complete visible rows");
|
|
219
|
+
}
|
|
220
|
+
const grid = current.grid.slice(0, rows);
|
|
221
|
+
const indices = new Set<number>();
|
|
222
|
+
for (const change of patch.rows) {
|
|
223
|
+
if (indices.has(change.index) || change.index >= rows || change.row.cells.length !== cols)
|
|
224
|
+
throw new TypeError("Invalid projection row patch");
|
|
225
|
+
indices.add(change.index);
|
|
226
|
+
const old = grid[change.index];
|
|
227
|
+
grid[change.index] =
|
|
228
|
+
old && terminalReplicaRowsEqual(old, change.row) ? old : freezeTerminalReplicaRow(change.row);
|
|
229
|
+
}
|
|
230
|
+
const candidate = {
|
|
231
|
+
...current,
|
|
232
|
+
cols,
|
|
233
|
+
rows,
|
|
234
|
+
grid,
|
|
235
|
+
cursor: patch.cursor ?? current.cursor,
|
|
236
|
+
modes: patch.modes ?? current.modes,
|
|
237
|
+
placements: patch.placements ?? current.placements,
|
|
238
|
+
bootstrap: patch.bootstrap ?? current.bootstrap,
|
|
239
|
+
deferredHistory: patch.deferredHistory,
|
|
240
|
+
};
|
|
241
|
+
// Validate without using the parsed copy so unchanged immutable rows retain identity.
|
|
242
|
+
validateViewportShape(grid, projectionMetadata(candidate));
|
|
243
|
+
return freezeProjection(candidate);
|
|
244
|
+
}
|
|
245
|
+
export interface TerminalProjectionState {
|
|
246
|
+
readonly receipt: TerminalProjectionReceipt;
|
|
247
|
+
readonly projection: TerminalProjectionSnapshot | null;
|
|
248
|
+
}
|
|
249
|
+
export function hashTerminalProjectionTombstone(reason: string): string {
|
|
250
|
+
return hashCanonicalTerminalValue(["terminal-projection-tombstone-v1", reason]);
|
|
251
|
+
}
|
|
252
|
+
/** Atomic projection commit. Only this verified path creates a receipt; failures leave current intact. */
|
|
253
|
+
export function commitTerminalProjection(
|
|
254
|
+
current: TerminalProjectionState | null,
|
|
255
|
+
envelope: TerminalProjectionEnvelope,
|
|
256
|
+
bytes: Uint8Array,
|
|
257
|
+
): TerminalProjectionState {
|
|
258
|
+
const frame = TerminalProjectionEnvelopeSchemaZ.parse(envelope);
|
|
259
|
+
if (
|
|
260
|
+
bytes.length !== frame.representationBytes ||
|
|
261
|
+
hashTerminalDeliveryRepresentation(bytes) !== frame.representationHash
|
|
262
|
+
)
|
|
263
|
+
throw new TypeError("Projection representation mismatch");
|
|
264
|
+
const value = decodeTerminalProjection(bytes);
|
|
265
|
+
if (
|
|
266
|
+
value.frame !== frame.frame ||
|
|
267
|
+
value.revision !== frame.canonicalRevision ||
|
|
268
|
+
(value.frame === "seed"
|
|
269
|
+
? frame.baseRevision !== null
|
|
270
|
+
: value.baseRevision !== frame.baseRevision)
|
|
271
|
+
)
|
|
272
|
+
throw new TypeError("Projection envelope mismatch");
|
|
273
|
+
if (current) {
|
|
274
|
+
for (const key of [
|
|
275
|
+
"workspaceName",
|
|
276
|
+
"semanticPaneId",
|
|
277
|
+
"generation",
|
|
278
|
+
"incarnation",
|
|
279
|
+
"deliveryNonce",
|
|
280
|
+
] as const)
|
|
281
|
+
if (current.receipt[key] !== frame[key]) throw new TypeError("Projection lifetime mismatch");
|
|
282
|
+
if (
|
|
283
|
+
value.revision < current.receipt.canonicalRevision ||
|
|
284
|
+
(value.revision === current.receipt.canonicalRevision &&
|
|
285
|
+
(frame.canonicalStateHash !== current.receipt.canonicalStateHash ||
|
|
286
|
+
frame.projectionStateHash !== current.receipt.projectionStateHash))
|
|
287
|
+
)
|
|
288
|
+
throw new TypeError("Stale projection revision");
|
|
289
|
+
if (current.projection === null) throw new TypeError("Projection lifetime is closed");
|
|
290
|
+
}
|
|
291
|
+
if (
|
|
292
|
+
value.frame !== "seed" &&
|
|
293
|
+
(!current || current.receipt.canonicalRevision !== value.baseRevision)
|
|
294
|
+
)
|
|
295
|
+
throw new TypeError("Projection baseline mismatch");
|
|
296
|
+
const projection =
|
|
297
|
+
value.frame === "seed"
|
|
298
|
+
? value.projection
|
|
299
|
+
: value.frame === "patch"
|
|
300
|
+
? applyTerminalProjectionPatch(current!.projection!, value.patch)
|
|
301
|
+
: null;
|
|
302
|
+
const projectionHash = projection
|
|
303
|
+
? hashTerminalProjection(projection)
|
|
304
|
+
: hashTerminalProjectionTombstone(value.frame === "tombstone" ? value.tombstone.reason : "");
|
|
305
|
+
const sourceHash = projection
|
|
306
|
+
? hashSourceSnapshotFromProjection(projection)
|
|
307
|
+
: hashTerminalReplicaTombstone(value.frame === "tombstone" ? value.tombstone.reason : "");
|
|
308
|
+
if (projectionHash !== frame.projectionStateHash || sourceHash !== frame.canonicalStateHash)
|
|
309
|
+
throw new TypeError("Projection state proof mismatch");
|
|
310
|
+
const receipt = TerminalProjectionReceiptSchemaZ.parse({
|
|
311
|
+
type: "terminal.projection.receipt",
|
|
312
|
+
workspaceName: frame.workspaceName,
|
|
313
|
+
semanticPaneId: frame.semanticPaneId,
|
|
314
|
+
generation: frame.generation,
|
|
315
|
+
incarnation: frame.incarnation,
|
|
316
|
+
deliveryNonce: frame.deliveryNonce,
|
|
317
|
+
transactionId: frame.transactionId,
|
|
318
|
+
canonicalRevision: frame.canonicalRevision,
|
|
319
|
+
canonicalStateHash: frame.canonicalStateHash,
|
|
320
|
+
projectionStateHash: frame.projectionStateHash,
|
|
321
|
+
representationHash: frame.representationHash,
|
|
322
|
+
});
|
|
323
|
+
return Object.freeze({ projection, receipt: Object.freeze(receipt) });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function rowCheckpoints(
|
|
327
|
+
writer: BinaryTerminalWriter,
|
|
328
|
+
options: { readonly yieldControl: () => Promise<void>; readonly signal?: AbortSignal },
|
|
329
|
+
) {
|
|
330
|
+
let cells = 0,
|
|
331
|
+
bytes = writer.byteLength;
|
|
332
|
+
return async (length: number) => {
|
|
333
|
+
cells += length;
|
|
334
|
+
if (cells >= 256 || writer.byteLength - bytes >= 32 * 1024) {
|
|
335
|
+
cells = 0;
|
|
336
|
+
bytes = writer.byteLength;
|
|
337
|
+
await options.yieldControl();
|
|
338
|
+
options.signal?.throwIfAborted();
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Task checkpoints bound cell validation and emission even for a single giant row. */
|
|
344
|
+
async function encodeProjectionCooperatively(
|
|
345
|
+
input: TerminalProjectionPayload,
|
|
346
|
+
options: { readonly yieldControl: () => Promise<void>; readonly signal?: AbortSignal },
|
|
347
|
+
): Promise<Uint8Array> {
|
|
348
|
+
options.signal?.throwIfAborted();
|
|
349
|
+
if (input.frame === "tombstone") return encodeTerminalProjection(input);
|
|
350
|
+
if (input.frame === "patch") {
|
|
351
|
+
const { patch, ...header } = input;
|
|
352
|
+
const parsedHeader = z
|
|
353
|
+
.object({
|
|
354
|
+
frame: z.literal("patch"),
|
|
355
|
+
revision: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
356
|
+
baseRevision: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
357
|
+
})
|
|
358
|
+
.strict()
|
|
359
|
+
.parse(header);
|
|
360
|
+
if (parsedHeader.revision <= parsedHeader.baseRevision)
|
|
361
|
+
throw new TypeError("Projection revision must advance");
|
|
362
|
+
const { rows, ...metadata } = patch;
|
|
363
|
+
const { rows: _rowSchema, ...shape } = TerminalProjectionPatchSchemaZ.shape;
|
|
364
|
+
void _rowSchema;
|
|
365
|
+
const parsed = z.object(shape).strict().parse(metadata);
|
|
366
|
+
if (
|
|
367
|
+
!Array.isArray(rows) ||
|
|
368
|
+
rows.length > 4096 ||
|
|
369
|
+
(parsed.dimensions && (parsed.dimensions.cols > 4096 || parsed.dimensions.rows > 4096))
|
|
370
|
+
)
|
|
371
|
+
throw new TypeError("Projection patch bound");
|
|
372
|
+
let cells = 0;
|
|
373
|
+
const indices = new Set<number>();
|
|
374
|
+
const writer = new BinaryTerminalWriter();
|
|
375
|
+
writer.header("patch", parsedHeader.revision, parsedHeader.baseRevision, "TCP1");
|
|
376
|
+
writer.array(7);
|
|
377
|
+
writer.value(parsed.dimensions ? [parsed.dimensions.cols, parsed.dimensions.rows] : null);
|
|
378
|
+
writer.array(rows.length);
|
|
379
|
+
const checkpoint = rowCheckpoints(writer, options);
|
|
380
|
+
for (const entry of rows) {
|
|
381
|
+
const parsedEntry = z
|
|
382
|
+
.object({ index: z.number().int().min(0).max(4095), row: z.unknown() })
|
|
383
|
+
.strict()
|
|
384
|
+
.parse(entry);
|
|
385
|
+
if (indices.has(parsedEntry.index)) throw new TypeError("Duplicate projection row");
|
|
386
|
+
indices.add(parsedEntry.index);
|
|
387
|
+
cells += entry.row.cells.length;
|
|
388
|
+
if (cells > 1_000_000) throw new TypeError("Projection cell bound");
|
|
389
|
+
writer.array(2);
|
|
390
|
+
writer.value(parsedEntry.index);
|
|
391
|
+
await writeTerminalProjectionRowCooperatively(
|
|
392
|
+
writer,
|
|
393
|
+
entry.row,
|
|
394
|
+
parsed.dimensions?.cols ?? null,
|
|
395
|
+
options,
|
|
396
|
+
);
|
|
397
|
+
await checkpoint(entry.row.cells.length);
|
|
398
|
+
}
|
|
399
|
+
const tuple = encodeTerminalProjectionPatchTuple({ ...parsed, rows: [] });
|
|
400
|
+
writer.value(tuple[2]);
|
|
401
|
+
writer.value(tuple[3]);
|
|
402
|
+
if (tuple[4] === null) writer.value(null);
|
|
403
|
+
else {
|
|
404
|
+
const placements = tuple[4] as readonly unknown[];
|
|
405
|
+
writer.array(placements.length);
|
|
406
|
+
for (const value of placements) {
|
|
407
|
+
writer.value(value);
|
|
408
|
+
await options.yieldControl();
|
|
409
|
+
options.signal?.throwIfAborted();
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
writer.value(tuple[5]);
|
|
413
|
+
writer.value([parsed.deferredHistory.rows, parsed.deferredHistory.digest]);
|
|
414
|
+
options.signal?.throwIfAborted();
|
|
415
|
+
return writer.finish();
|
|
416
|
+
}
|
|
417
|
+
const { projection, ...header } = input;
|
|
418
|
+
const parsedHeader = z
|
|
419
|
+
.object({
|
|
420
|
+
frame: z.literal("seed"),
|
|
421
|
+
revision: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
422
|
+
})
|
|
423
|
+
.strict()
|
|
424
|
+
.parse(header);
|
|
425
|
+
const { grid, ...metadata } = projection;
|
|
426
|
+
const { grid: _gridSchema, ...metadataShape } = TerminalProjectionSnapshotSchemaZ.shape;
|
|
427
|
+
void _gridSchema;
|
|
428
|
+
const parsed = z.object(metadataShape).strict().parse(metadata);
|
|
429
|
+
if (
|
|
430
|
+
!Array.isArray(grid) ||
|
|
431
|
+
grid.length !== parsed.rows ||
|
|
432
|
+
parsed.cols * parsed.rows > 1_000_000 ||
|
|
433
|
+
parsed.cursor.x >= parsed.cols ||
|
|
434
|
+
parsed.cursor.y >= parsed.rows ||
|
|
435
|
+
parsed.placements.some(
|
|
436
|
+
(p) => p.row + p.rows > parsed.rows || p.column + p.columns > parsed.cols,
|
|
437
|
+
)
|
|
438
|
+
)
|
|
439
|
+
throw new TypeError("Invalid projection dimensions");
|
|
440
|
+
const writer = new BinaryTerminalWriter();
|
|
441
|
+
writer.header("seed", parsedHeader.revision, undefined, "TCP1");
|
|
442
|
+
writer.array(8);
|
|
443
|
+
writer.value(parsed.cols);
|
|
444
|
+
writer.value(parsed.rows);
|
|
445
|
+
writer.array(grid.length);
|
|
446
|
+
const checkpoint = rowCheckpoints(writer, options);
|
|
447
|
+
for (const row of grid) {
|
|
448
|
+
await writeTerminalProjectionRowCooperatively(writer, row, parsed.cols, options);
|
|
449
|
+
await checkpoint(row.cells.length);
|
|
450
|
+
}
|
|
451
|
+
const tuple = encodeTerminalProjectionMetadataTuple(parsed);
|
|
452
|
+
writer.value(tuple[0]);
|
|
453
|
+
writer.value(tuple[1]);
|
|
454
|
+
const placements = tuple[2] as readonly unknown[];
|
|
455
|
+
writer.array(placements.length);
|
|
456
|
+
for (const placement of placements) {
|
|
457
|
+
writer.value(placement);
|
|
458
|
+
await options.yieldControl();
|
|
459
|
+
options.signal?.throwIfAborted();
|
|
460
|
+
}
|
|
461
|
+
writer.value(tuple[3]);
|
|
462
|
+
writer.value([parsed.deferredHistory.rows, parsed.deferredHistory.digest]);
|
|
463
|
+
options.signal?.throwIfAborted();
|
|
464
|
+
return writer.finish();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export async function encodeTerminalProjectionCooperatively(
|
|
468
|
+
input: TerminalProjectionPayload,
|
|
469
|
+
options: { readonly yieldControl: () => Promise<void>; readonly signal?: AbortSignal },
|
|
470
|
+
): Promise<Uint8Array> {
|
|
471
|
+
try {
|
|
472
|
+
return await encodeProjectionCooperatively(input, options);
|
|
473
|
+
} catch (error) {
|
|
474
|
+
return projectionEncodingError(error);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
@@ -7,6 +7,8 @@ import { createBufferedFnv64, type BufferedFnv64 } from "./terminal-fnv64-wasm.t
|
|
|
7
7
|
|
|
8
8
|
const ROW_HASH_CACHE = new WeakMap<object, string>();
|
|
9
9
|
const DEEPLY_FROZEN_ROWS = new WeakSet<object>();
|
|
10
|
+
// Provenance from scalar construction only, never generic frozen-object checks.
|
|
11
|
+
const DECODED_ROWS = new WeakSet<object>();
|
|
10
12
|
const UTF8_ENCODER = new TextEncoder();
|
|
11
13
|
// Fixed ASCII headers cover common terminal strings without retaining values.
|
|
12
14
|
const SHORT_ASCII_HEADERS = Array.from({ length: 33 }, (_, length) => `s${length}:`);
|
|
@@ -600,8 +602,9 @@ class DecodedTerminalReplicaRowBuilder {
|
|
|
600
602
|
cells: Object.freeze(cells),
|
|
601
603
|
wrapped,
|
|
602
604
|
}) as unknown as TerminalReplicaRow;
|
|
603
|
-
//
|
|
605
|
+
// Existing row hashing remains lazy and unchanged.
|
|
604
606
|
DEEPLY_FROZEN_ROWS.add(row);
|
|
607
|
+
DECODED_ROWS.add(row);
|
|
605
608
|
return row;
|
|
606
609
|
}
|
|
607
610
|
|
|
@@ -610,6 +613,10 @@ class DecodedTerminalReplicaRowBuilder {
|
|
|
610
613
|
}
|
|
611
614
|
}
|
|
612
615
|
|
|
616
|
+
export function isDecodedTerminalReplicaRow(row: unknown): row is TerminalReplicaRow {
|
|
617
|
+
return typeof row === "object" && row !== null && DECODED_ROWS.has(row);
|
|
618
|
+
}
|
|
619
|
+
|
|
613
620
|
export function createDecodedTerminalReplicaRowBuilder() {
|
|
614
621
|
return new DecodedTerminalReplicaRowBuilder();
|
|
615
622
|
}
|
|
@@ -1048,7 +1048,7 @@ function hashTerminalReplicaRow(
|
|
|
1048
1048
|
return hash;
|
|
1049
1049
|
}
|
|
1050
1050
|
|
|
1051
|
-
function hashTerminalReplicaRows(
|
|
1051
|
+
export function hashTerminalReplicaRows(
|
|
1052
1052
|
rows: readonly TerminalReplicaRow[],
|
|
1053
1053
|
profile?: MutableTerminalReplicaApplyProfile,
|
|
1054
1054
|
instrumentation?: TerminalReplicaApplyOptions["instrumentation"],
|
|
@@ -13,7 +13,8 @@ import { createGuardedNativeSplitResize } from "./guarded-native-split-resize.js
|
|
|
13
13
|
import { WindowLinkTargetSchemaZ } from "@tmux-ide/contracts";
|
|
14
14
|
import { WindowSplitLayoutUnavailable } from "../terminal/mirror/window-split-authority.js";
|
|
15
15
|
import { requireSupportedTmuxVersion } from "./tmux-version.js";
|
|
16
|
-
import { join } from "node:path";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
import { LocalImageOwner, cleanupLocalImageOwner } from "../terminal/graphics/local-image-owner.js";
|
|
17
18
|
import { createClaudeTeamMembershipReader } from "../terminal/attachments/claude-team-names.js";
|
|
18
19
|
import { PaneSourceDiscovery } from "./pane-source-discovery.js";
|
|
19
20
|
import { createBackgroundNativeCapture } from "./background-native-capture.js";
|
|
@@ -89,7 +90,7 @@ import { setActivationBackend } from "./active-projects.js";
|
|
|
89
90
|
import { readOrMintEnvironmentId } from "./environment-identity.js";
|
|
90
91
|
import { captureDaemonProvenance } from "./daemon-provenance.js";
|
|
91
92
|
import { registerLogSecret, setLogIdentity } from "./log.js";
|
|
92
|
-
import { canonicalDaemonUrl, clearCanonicalDaemonInfoIfOwned, clearCanonicalDaemonInfoIfUnchanged, inspectCanonicalDaemonInfo, isCanonicalDaemonAlive, isCanonicalDaemonRecordOwnerProvenDead, probeCanonicalDaemonHealth, probeCanonicalDaemonIdentity, releaseCanonicalDaemonClaim, tryAcquireCanonicalDaemonClaim, writeCanonicalDaemonInfo, matchesCanonicalDaemonPredecessor, } from "./canonical-daemon.js";
|
|
93
|
+
import { canonicalDaemonUrl, getCanonicalDaemonInfoPath, clearCanonicalDaemonInfoIfOwned, clearCanonicalDaemonInfoIfUnchanged, inspectCanonicalDaemonInfo, isCanonicalDaemonAlive, isCanonicalDaemonRecordOwnerProvenDead, probeCanonicalDaemonHealth, probeCanonicalDaemonIdentity, releaseCanonicalDaemonClaim, tryAcquireCanonicalDaemonClaim, writeCanonicalDaemonInfo, matchesCanonicalDaemonPredecessor, } from "./canonical-daemon.js";
|
|
93
94
|
const requireFromHere = createRequire(import.meta.url);
|
|
94
95
|
const DEFAULT_HOSTNAME = "127.0.0.1";
|
|
95
96
|
const DEFAULT_GRACEFUL_MS = 2000;
|
|
@@ -955,6 +956,7 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
955
956
|
};
|
|
956
957
|
let terminalAttachmentRuntime = null;
|
|
957
958
|
let paneStreamRuntime = null;
|
|
959
|
+
let localImages;
|
|
958
960
|
let serverOwners = null;
|
|
959
961
|
let defaultEditorContextRetired = false;
|
|
960
962
|
const defaultDiagnosticsLifetime = new AbortController();
|
|
@@ -1219,7 +1221,35 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1219
1221
|
return terminalAttachmentRuntime;
|
|
1220
1222
|
};
|
|
1221
1223
|
const peekTerminalAttachmentRuntime = () => terminalAttachmentRuntime;
|
|
1224
|
+
// The held canonical claim has already proven and hardened this private root.
|
|
1225
|
+
// Local images are optional: unavailable storage retains inline graphics.
|
|
1226
|
+
if (process.platform !== "win32") {
|
|
1227
|
+
const root = dirname(getCanonicalDaemonInfoPath());
|
|
1228
|
+
try {
|
|
1229
|
+
if (existingCanonical.status === "valid") {
|
|
1230
|
+
await cleanupLocalImageOwner({
|
|
1231
|
+
root,
|
|
1232
|
+
owner: {
|
|
1233
|
+
generation: existingCanonical.info.instanceId,
|
|
1234
|
+
pid: existingCanonical.info.pid,
|
|
1235
|
+
startedAt: existingCanonical.info.startedAt,
|
|
1236
|
+
},
|
|
1237
|
+
proveDead: () => isCanonicalDaemonRecordOwnerProvenDead(existingCanonical),
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
localImages = await LocalImageOwner.create({
|
|
1241
|
+
root,
|
|
1242
|
+
generation: instanceId,
|
|
1243
|
+
pid: process.pid,
|
|
1244
|
+
startedAt,
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
catch {
|
|
1248
|
+
localImages = undefined;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1222
1251
|
paneStreamRuntime = createPaneStreamRuntime({
|
|
1252
|
+
localImages,
|
|
1223
1253
|
daemonInstanceId: instanceId,
|
|
1224
1254
|
webSocketUrl: paneStreamWebSocketUrl(bindHostname, port),
|
|
1225
1255
|
sessionRuntimeRegistry,
|
|
@@ -1239,6 +1269,7 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1239
1269
|
});
|
|
1240
1270
|
await externalInteractionObserver.start();
|
|
1241
1271
|
serverOwners = await createEmbeddedTmuxServerOwners({
|
|
1272
|
+
localImages,
|
|
1242
1273
|
environmentId,
|
|
1243
1274
|
onDefaultScope: async (scope) => {
|
|
1244
1275
|
interactionEvidence = new InteractionEvidenceAuthority(environmentId, scope);
|
|
@@ -1514,7 +1545,10 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1514
1545
|
// The pane-stream coordinator may still hold runtime consumers while it
|
|
1515
1546
|
// drains. Preserve the normal shutdown order on startup rollback too:
|
|
1516
1547
|
// transports first, then the one session/control authority.
|
|
1517
|
-
await Promise.allSettled([
|
|
1548
|
+
await Promise.allSettled([
|
|
1549
|
+
sessionRuntimeRegistry?.dispose() ?? Promise.resolve(),
|
|
1550
|
+
localImages?.dispose() ?? Promise.resolve(),
|
|
1551
|
+
]);
|
|
1518
1552
|
throw error;
|
|
1519
1553
|
}
|
|
1520
1554
|
const { server, sockets, closeClients, closeWsServers, terminalAttachmentBoundary, paneStreamBoundary, } = startedServer;
|
|
@@ -1535,6 +1569,7 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1535
1569
|
...(await retireTerminalAttachmentTransport(terminalAttachmentRuntime, terminalAttachmentBoundary)),
|
|
1536
1570
|
...(await retirePaneStreamTransport()),
|
|
1537
1571
|
];
|
|
1572
|
+
await localImages?.dispose();
|
|
1538
1573
|
terminalInventoryRuntime.dispose();
|
|
1539
1574
|
const paneDisposal = Promise.resolve().then(() => workspacePaneCreation.dispose());
|
|
1540
1575
|
const workspaceOpenDisposal = Promise.resolve().then(() => workspaceOpen.dispose());
|
|
@@ -1785,6 +1820,7 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1785
1820
|
// listener before touching the legacy WS or HTTP surfaces.
|
|
1786
1821
|
failures.push(...(await retireTerminalAttachmentTransport(terminalAttachmentRuntime, terminalAttachmentBoundary)));
|
|
1787
1822
|
failures.push(...(await retirePaneStreamTransport()));
|
|
1823
|
+
await capture(() => localImages?.dispose());
|
|
1788
1824
|
await capture(() => terminalInventoryRuntime.dispose());
|
|
1789
1825
|
await capture(() => workspacePaneCreation.dispose());
|
|
1790
1826
|
await capture(() => workspaceOpen.dispose());
|
|
@@ -58,6 +58,7 @@ export async function createEmbeddedTmuxServerOwners(options) {
|
|
|
58
58
|
await retireDefault();
|
|
59
59
|
const route = tmuxServerPaneStreamPath(scope);
|
|
60
60
|
const owner = await createNativeTmuxServerOwner({
|
|
61
|
+
localImages: options.localImages,
|
|
61
62
|
environmentId: options.environmentId,
|
|
62
63
|
...scope,
|
|
63
64
|
tmuxAuthority: observation.authority,
|
|
@@ -369,6 +369,7 @@ export async function createNativeTmuxServerOwner(options) {
|
|
|
369
369
|
}),
|
|
370
370
|
});
|
|
371
371
|
const paneStreamRuntime = createPaneStreamRuntime({
|
|
372
|
+
localImages: options.localImages,
|
|
372
373
|
daemonInstanceId: generation,
|
|
373
374
|
webSocketUrl: options.webSocketUrl,
|
|
374
375
|
sessionRuntimeRegistry,
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
<key>CFBundlePackageType</key>
|
|
22
22
|
<string>APPL</string>
|
|
23
23
|
<key>CFBundleShortVersionString</key>
|
|
24
|
-
<string>2.9.
|
|
24
|
+
<string>2.9.12</string>
|
|
25
25
|
<key>CFBundleVersion</key>
|
|
26
|
-
<string>2.9.
|
|
26
|
+
<string>2.9.12</string>
|
|
27
27
|
<key>LSMinimumSystemVersion</key>
|
|
28
28
|
<string>11.0</string>
|
|
29
29
|
<key>LSUIElement</key>
|
|
Binary file
|
|
Binary file
|
package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
</data>
|
|
11
11
|
<key>Resources/Assets.car</key>
|
|
12
12
|
<data>
|
|
13
|
-
|
|
13
|
+
4qUHjN0ukJdS6FxJvaGZD8XzSrI=
|
|
14
14
|
</data>
|
|
15
15
|
</dict>
|
|
16
16
|
<key>files2</key>
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
<dict>
|
|
27
27
|
<key>hash2</key>
|
|
28
28
|
<data>
|
|
29
|
-
|
|
29
|
+
cnvGMeWWQfA0LC/wWkAWWggL5Acd/+3ysR+9aaOtMWg=
|
|
30
30
|
</data>
|
|
31
31
|
</dict>
|
|
32
32
|
</dict>
|