@astrosheep/square 0.3.93 → 0.3.95
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/activity-feed.d.ts +3 -5
- package/dist/activity-feed.js +16 -14
- package/dist/artifact.js +3 -3
- package/dist/attention.d.ts +19 -0
- package/dist/attention.js +30 -0
- package/dist/boundary-presentation.d.ts +2 -2
- package/dist/boundary-presentation.js +33 -40
- package/dist/catch-decisions.d.ts +5 -8
- package/dist/catch-decisions.js +45 -25
- package/dist/claude-delivery-status.js +10 -4
- package/dist/claude-delivery.js +19 -11
- package/dist/claude-mod.js +24 -53
- package/dist/cli/context.d.ts +9 -4
- package/dist/cli/context.js +30 -27
- package/dist/cli/express-presentation.d.ts +3 -1
- package/dist/cli/express-presentation.js +28 -16
- package/dist/cli/json.d.ts +3 -0
- package/dist/cli/json.js +14 -0
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.d.ts +1 -3
- package/dist/cli/observation-commands.js +43 -56
- package/dist/cli/program.js +3 -3
- package/dist/cli/registry.js +7 -6
- package/dist/cli/square-commands.d.ts +0 -2
- package/dist/cli/square-commands.js +30 -44
- package/dist/codex-hook.d.ts +3 -2
- package/dist/codex-hook.js +13 -4
- package/dist/codex-queue.js +4 -4
- package/dist/decisions.d.ts +5 -16
- package/dist/decisions.js +9 -33
- package/dist/delivery-attempts.d.ts +52 -0
- package/dist/delivery-attempts.js +73 -0
- package/dist/delivery-deferral.js +2 -2
- package/dist/delivery-effects.d.ts +139 -0
- package/dist/delivery-effects.js +163 -0
- package/dist/delivery-health.d.ts +0 -1
- package/dist/delivery-health.js +1 -3
- package/dist/delivery-operations.d.ts +2 -2
- package/dist/delivery-operations.js +76 -101
- package/dist/delivery-primitives.d.ts +5 -0
- package/dist/delivery-primitives.js +3 -0
- package/dist/delivery.d.ts +1 -1
- package/dist/delivery.js +7 -6
- package/dist/help.js +14 -14
- package/dist/http-delivery.d.ts +16 -0
- package/dist/http-delivery.js +101 -0
- package/dist/inbox.d.ts +9 -22
- package/dist/inbox.js +23 -27
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.js +23 -24
- package/dist/model.d.ts +12 -21
- package/dist/model.js +5 -8
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.js +8 -5
- package/dist/operation-presentation.d.ts +6 -6
- package/dist/operation-presentation.js +55 -58
- package/dist/owner.d.ts +11 -0
- package/dist/owner.js +32 -0
- package/dist/paseo-delivery.d.ts +4 -1
- package/dist/paseo-delivery.js +7 -12
- package/dist/paseo-state.d.ts +2 -2
- package/dist/paseo-state.js +6 -4
- package/dist/plugins/opencode/delivery.js +27 -15
- package/dist/ports.d.ts +18 -47
- package/dist/presentation-operations.d.ts +14 -19
- package/dist/presentation-operations.js +51 -61
- package/dist/presentation.d.ts +7 -17
- package/dist/presentation.js +46 -91
- package/dist/receive.d.ts +66 -0
- package/dist/receive.js +54 -0
- package/dist/runtime.d.ts +1 -8
- package/dist/runtime.js +2 -17
- package/dist/seats.d.ts +2 -3
- package/dist/seats.js +7 -15
- package/dist/session-lifecycle.d.ts +6 -2
- package/dist/session-lifecycle.js +39 -11
- package/dist/sqlite-square.d.ts +36 -23
- package/dist/sqlite-square.js +162 -211
- package/dist/square-api.d.ts +16 -11
- package/dist/square-api.js +27 -23
- package/dist/square-core.d.ts +0 -14
- package/dist/square-core.js +0 -9
- package/dist/square-domain.d.ts +4 -2
- package/dist/square-domain.js +65 -52
- package/dist/square-facade.d.ts +47 -105
- package/dist/square-index.d.ts +5 -13
- package/dist/square-index.js +13 -38
- package/dist/square-legacy-framing.d.ts +2 -0
- package/dist/square-legacy-framing.js +8 -0
- package/dist/square-location.d.ts +1 -14
- package/dist/square-location.js +4 -70
- package/dist/square-migration-host-ledger.d.ts +2 -2
- package/dist/square-migration-host-ledger.js +6 -5
- package/dist/square-migration.d.ts +4 -1
- package/dist/square-migration.js +93 -38
- package/dist/square-receive.d.ts +6 -0
- package/dist/square-receive.js +18 -0
- package/dist/square-sqlite.d.ts +9 -3
- package/dist/square-sqlite.js +139 -114
- package/dist/square-tools.d.ts +7 -10
- package/dist/square-tools.js +21 -61
- package/dist/stream.js +4 -5
- package/dist/views.d.ts +23 -13
- package/dist/views.js +47 -53
- package/dist/wakes.d.ts +1 -5
- package/dist/wakes.js +4 -6
- package/examples/symmetric-domains.mjs +250 -0
- package/guides/application-facade-delivery-evidence.md +6 -6
- package/guides/attention-primitive.md +32 -0
- package/guides/change-waits.md +2 -2
- package/guides/claude-native.md +18 -9
- package/guides/codex-delivery.md +17 -6
- package/guides/delivery-primitives.md +180 -0
- package/guides/mcp-connection-lifetime.md +1 -1
- package/guides/mcp.md +3 -3
- package/guides/migration.md +148 -151
- package/guides/operation-cancellation.md +1 -1
- package/guides/participant-ownership.md +78 -47
- package/guides/pi-tools.md +34 -5
- package/guides/primitive-catch-consolidation.md +5 -5
- package/guides/primitive-history-consolidation.md +2 -2
- package/guides/primitive-status-consolidation.md +3 -3
- package/guides/primitive-storage-consolidation.md +1 -1
- package/guides/receive-primitive.md +90 -0
- package/guides/row-runtime-delivery-evidence.md +1 -1
- package/guides/row-storage-design.md +22 -28
- package/guides/square-api.md +314 -91
- package/guides/symmetric-domains.md +92 -0
- package/package.json +14 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/skills/square/SKILL.md +2 -6
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +27 -17
- package/plugins/paseo/client/palette.ts +18 -18
- package/plugins/paseo/client/square-message.tsx +17 -23
- package/plugins/pi/square-pi.js +17 -17
- package/skills/square/SKILL.md +2 -6
- package/dist/square-backend.d.ts +0 -13
- package/dist/square-backend.js +0 -1
- package/dist/square-stdio.d.ts +0 -22
- package/dist/square-stdio.js +0 -235
- package/dist/stdio-server.d.ts +0 -3
- package/dist/stdio-server.js +0 -212
- package/guides/backends.md +0 -193
package/dist/square-facade.d.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import type { SquareBackends } from './square-backend.js';
|
|
2
1
|
import type { DeliveryDeferral, MetadataMatch } from './delivery-deferral.js';
|
|
3
2
|
export type { DeliveryDeferral, MetadataMatch, MetadataScalar } from './delivery-deferral.js';
|
|
4
3
|
import type { ActivityId, Perception, Reach } from './square-core.js';
|
|
5
|
-
import type {
|
|
6
|
-
import type { WakeTransportPort,
|
|
4
|
+
import type { Owner, StoredAct, WakeAddress, ActivityMetadata, DirectedNotificationRoute } from './model.js';
|
|
5
|
+
import type { WakeTransportPort, SquareResourceOwner } from './ports.js';
|
|
7
6
|
export type { ActivityId, Perception, Reach } from './square-core.js';
|
|
8
|
-
export type {
|
|
9
|
-
/** Indexed archive event, including read
|
|
7
|
+
export type { Owner, WakeAddress, ActivityMetadata, JsonValue, DirectedNotificationRoute } from './model.js';
|
|
8
|
+
/** Indexed archive event, including read bookkeeping. */
|
|
10
9
|
export type ArchiveRecord = Readonly<StoredAct>;
|
|
11
10
|
export interface Activity {
|
|
12
11
|
readonly id: ActivityId;
|
|
13
12
|
readonly at: number;
|
|
14
|
-
readonly kind: 'join' | 'say' | 'done' | '
|
|
13
|
+
readonly kind: 'join' | 'say' | 'done' | 'listen' | 'ignore' | 'unlisten';
|
|
15
14
|
readonly actor: string;
|
|
16
15
|
readonly body?: string;
|
|
17
16
|
readonly metadata?: ActivityMetadata;
|
|
@@ -22,6 +21,7 @@ export interface Activity {
|
|
|
22
21
|
}
|
|
23
22
|
export interface PerceivedActivity extends Activity {
|
|
24
23
|
readonly perception: Perception;
|
|
24
|
+
readonly via?: DirectedNotificationRoute;
|
|
25
25
|
}
|
|
26
26
|
/** Request extra facts for rendering, without changing operation semantics. */
|
|
27
27
|
export interface ObservationOptions {
|
|
@@ -39,12 +39,8 @@ export interface ExpressOptions extends ObservationOptions {
|
|
|
39
39
|
export interface ExpressInput extends ExpressOptions {
|
|
40
40
|
readonly body?: string;
|
|
41
41
|
}
|
|
42
|
-
export interface
|
|
42
|
+
export interface ExpressResult {
|
|
43
43
|
readonly activity: Activity;
|
|
44
|
-
readonly delivery?: DeliveryResult;
|
|
45
|
-
}
|
|
46
|
-
export interface ExpressResult extends ActivityResult {
|
|
47
|
-
readonly waited: boolean;
|
|
48
44
|
/** The replied-to author was not standing when the reply landed. */
|
|
49
45
|
readonly replyAuthorNotHere?: string;
|
|
50
46
|
/** Body @name tokens with no standing match at landing; never stored on the activity. */
|
|
@@ -61,8 +57,9 @@ export interface ListeningResult {
|
|
|
61
57
|
readonly listening: readonly string[];
|
|
62
58
|
readonly observation?: SquareObservation;
|
|
63
59
|
}
|
|
64
|
-
export interface LifecycleResult
|
|
65
|
-
readonly
|
|
60
|
+
export interface LifecycleResult {
|
|
61
|
+
readonly activity: Activity;
|
|
62
|
+
readonly observation?: SquareObservation;
|
|
66
63
|
}
|
|
67
64
|
export interface CatchOptions extends ObservationOptions {
|
|
68
65
|
readonly id?: ActivityId;
|
|
@@ -72,12 +69,11 @@ export interface CatchOptions extends ObservationOptions {
|
|
|
72
69
|
readonly limit?: number;
|
|
73
70
|
readonly replace?: boolean;
|
|
74
71
|
}
|
|
72
|
+
export type CatchReason = 'drained' | 'idle' | 'capped' | 'quorum' | 'replaced' | 'already-active';
|
|
75
73
|
export interface CatchResult {
|
|
76
74
|
readonly activities: readonly PerceivedActivity[];
|
|
77
|
-
readonly consumedThrough: ActivityId | null;
|
|
78
|
-
readonly idleExpired: boolean;
|
|
79
75
|
readonly remaining: number;
|
|
80
|
-
readonly
|
|
76
|
+
readonly reason: CatchReason;
|
|
81
77
|
/** Occasional participant-facing nudge; not part of any activity. */
|
|
82
78
|
readonly hint?: string;
|
|
83
79
|
readonly observation?: CatchObservation;
|
|
@@ -89,10 +85,8 @@ export interface CatchWaitStart {
|
|
|
89
85
|
}
|
|
90
86
|
export type OperationProgress = {
|
|
91
87
|
readonly kind: 'waiting';
|
|
92
|
-
readonly reason: '
|
|
88
|
+
readonly reason: 'throttled';
|
|
93
89
|
readonly delayMs?: number;
|
|
94
|
-
readonly holder?: string;
|
|
95
|
-
readonly holdReason?: string;
|
|
96
90
|
} | ({
|
|
97
91
|
readonly kind: 'catch-started';
|
|
98
92
|
readonly participant: string;
|
|
@@ -134,32 +128,17 @@ export interface HistoryResult {
|
|
|
134
128
|
readonly activities: readonly Activity[];
|
|
135
129
|
readonly observation?: HistoryObservation;
|
|
136
130
|
}
|
|
137
|
-
export interface ParticipantStatus {
|
|
138
|
-
readonly name: string;
|
|
139
|
-
readonly state: 'joined' | 'done';
|
|
140
|
-
readonly consumedThrough: ActivityId | null;
|
|
141
|
-
readonly watching: boolean;
|
|
142
|
-
readonly listening: readonly string[];
|
|
143
|
-
}
|
|
144
131
|
export interface SquareSnapshot {
|
|
145
132
|
readonly context: string;
|
|
146
133
|
readonly actCount: number;
|
|
147
134
|
readonly hardCap: number | null;
|
|
148
135
|
readonly throttlePerMinute?: number;
|
|
149
|
-
readonly
|
|
150
|
-
readonly by: string;
|
|
151
|
-
readonly reason?: string;
|
|
152
|
-
} | null;
|
|
153
|
-
readonly participants: readonly ParticipantStatus[];
|
|
136
|
+
readonly participants: readonly Pick<ParticipantSummary, 'name' | 'presence' | 'listening'>[];
|
|
154
137
|
delivered(name: string, id: ActivityId): boolean;
|
|
155
138
|
}
|
|
156
139
|
export interface OpenOptions {
|
|
157
140
|
readonly clock?: () => number;
|
|
158
|
-
readonly wakeTransport?: WakeTransportPort;
|
|
159
|
-
/** Explicit execution coordinate resolved by an adapter. */
|
|
160
|
-
readonly body?: Body;
|
|
161
|
-
/** Optional wake address attached to this body. */
|
|
162
|
-
readonly wakeAddress?: WakeAddress;
|
|
141
|
+
readonly wakeTransport?: WakeTransportPort | false;
|
|
163
142
|
}
|
|
164
143
|
export interface SquareLocation {
|
|
165
144
|
readonly path?: string;
|
|
@@ -167,8 +146,6 @@ export interface SquareLocation {
|
|
|
167
146
|
readonly env?: NodeJS.ProcessEnv;
|
|
168
147
|
}
|
|
169
148
|
export interface SquareAtInput extends SquareLocation, OpenOptions {
|
|
170
|
-
readonly backends?: SquareBackends;
|
|
171
|
-
readonly participant?: string;
|
|
172
149
|
/** A caller-owned connection; closing this Square never closes its owner. */
|
|
173
150
|
readonly resources?: SquareResourceOwner;
|
|
174
151
|
}
|
|
@@ -180,6 +157,8 @@ export interface SquareBuildInput extends SquareAtInput {
|
|
|
180
157
|
readonly force?: boolean;
|
|
181
158
|
}
|
|
182
159
|
export interface JoinOptions extends ObservationOptions {
|
|
160
|
+
readonly owner: Owner;
|
|
161
|
+
readonly wakeAddress?: WakeAddress;
|
|
183
162
|
readonly takeover?: boolean;
|
|
184
163
|
readonly last?: number;
|
|
185
164
|
}
|
|
@@ -187,8 +166,6 @@ export interface JoinResult {
|
|
|
187
166
|
readonly participant: Participant;
|
|
188
167
|
readonly activity: Activity | null;
|
|
189
168
|
readonly kind: 'joined' | 'reconnected' | 'taken-over';
|
|
190
|
-
/** Known joined-or-done participants, not the current standing count. */
|
|
191
|
-
readonly participantCount: number;
|
|
192
169
|
readonly observation?: JoinObservation;
|
|
193
170
|
}
|
|
194
171
|
export interface Participant {
|
|
@@ -200,9 +177,6 @@ export interface Participant {
|
|
|
200
177
|
unlisten(target: string, options?: ObservationOptions, control?: OperationControl): Promise<ListenerChangeResult>;
|
|
201
178
|
listening(options?: ObservationOptions, control?: OperationControl): Promise<ListeningResult>;
|
|
202
179
|
catch(options?: CatchOptions, control?: OperationControl): Promise<CatchResult>;
|
|
203
|
-
history(query?: HistoryQuery, control?: OperationControl): Promise<HistoryResult>;
|
|
204
|
-
hold(reason?: string, options?: ObservationOptions, control?: OperationControl): Promise<LifecycleResult>;
|
|
205
|
-
resume(options?: ObservationOptions, control?: OperationControl): Promise<LifecycleResult>;
|
|
206
180
|
/** Leave without a message. Only an omitted or empty body is accepted. */
|
|
207
181
|
done(body?: string, options?: ObservationOptions, control?: OperationControl): Promise<LifecycleResult>;
|
|
208
182
|
deferDelivery(input: Readonly<{
|
|
@@ -212,12 +186,15 @@ export interface Participant {
|
|
|
212
186
|
renewDeliveryDeferral(id: string, durationMs: number, control?: OperationControl): Promise<DeliveryDeferral | null>;
|
|
213
187
|
releaseDeliveryDeferral(id: string, control?: OperationControl): Promise<boolean>;
|
|
214
188
|
}
|
|
215
|
-
export
|
|
216
|
-
|
|
217
|
-
|
|
189
|
+
export interface HeldSeat {
|
|
190
|
+
readonly name: string;
|
|
191
|
+
readonly generation: ActivityId;
|
|
192
|
+
readonly attachment: 'attached' | 'detached';
|
|
193
|
+
}
|
|
194
|
+
export type ParticipantPresence = 'never-joined' | 'active' | 'watching' | 'away' | 'done';
|
|
218
195
|
export interface ParticipantSummary {
|
|
219
196
|
readonly name: string;
|
|
220
|
-
readonly
|
|
197
|
+
readonly deliveryAvailable: boolean;
|
|
221
198
|
readonly listening: readonly string[];
|
|
222
199
|
readonly activityCount: number;
|
|
223
200
|
readonly lastActiveAt?: number;
|
|
@@ -232,16 +209,9 @@ export interface StatusResult {
|
|
|
232
209
|
/** Present when a viewer resolves; an empty list means they listen to nobody. */
|
|
233
210
|
readonly listening?: readonly string[];
|
|
234
211
|
readonly hardCap: number | null;
|
|
235
|
-
readonly activeCount: number;
|
|
236
|
-
readonly doneCount: number;
|
|
237
212
|
readonly participants: readonly ParticipantSummary[];
|
|
238
213
|
readonly now: number;
|
|
239
214
|
readonly throttlePerMinute?: number;
|
|
240
|
-
readonly hold: {
|
|
241
|
-
readonly by?: string;
|
|
242
|
-
readonly reason?: string;
|
|
243
|
-
readonly at?: number;
|
|
244
|
-
} | null;
|
|
245
215
|
readonly latestActivity?: Activity;
|
|
246
216
|
readonly observation?: StatusObservation;
|
|
247
217
|
}
|
|
@@ -283,11 +253,8 @@ export interface InboxResult {
|
|
|
283
253
|
export interface RecoveryResult {
|
|
284
254
|
readonly participant?: string;
|
|
285
255
|
readonly standingCount?: number;
|
|
286
|
-
readonly held?: boolean;
|
|
287
|
-
readonly holdReason?: string;
|
|
288
256
|
readonly ownActivityCount?: number;
|
|
289
257
|
readonly hardCap?: number | null;
|
|
290
|
-
readonly roster?: readonly string[];
|
|
291
258
|
readonly activitySummaries?: readonly UnreadActivitySummary[];
|
|
292
259
|
}
|
|
293
260
|
export type StreamStart = {
|
|
@@ -295,90 +262,70 @@ export type StreamStart = {
|
|
|
295
262
|
readonly last: number;
|
|
296
263
|
} | {
|
|
297
264
|
readonly kind: 'after';
|
|
298
|
-
readonly after:
|
|
265
|
+
readonly after: ActivityId;
|
|
299
266
|
};
|
|
300
267
|
export interface StreamOptions {
|
|
301
268
|
readonly recipient?: string;
|
|
302
269
|
readonly start?: StreamStart;
|
|
303
270
|
}
|
|
304
|
-
export type StreamRecord =
|
|
305
|
-
readonly seq: number;
|
|
306
|
-
readonly square: string;
|
|
307
|
-
readonly route?: string;
|
|
308
|
-
};
|
|
309
|
-
export interface UnreadActivityPreview {
|
|
310
|
-
readonly number: number;
|
|
311
|
-
readonly act: Extract<ArchiveRecord, {
|
|
312
|
-
kind: 'say';
|
|
313
|
-
}>;
|
|
314
|
-
readonly perception: Perception;
|
|
315
|
-
}
|
|
271
|
+
export type StreamRecord = Activity | PerceivedActivity;
|
|
316
272
|
export interface UnreadActivitySummary {
|
|
317
273
|
readonly name: string;
|
|
318
274
|
readonly count: number;
|
|
319
275
|
readonly latestActivityAgeMs: number;
|
|
320
|
-
readonly
|
|
276
|
+
readonly preview: Extract<ArchiveRecord, {
|
|
277
|
+
kind: 'say';
|
|
278
|
+
}>;
|
|
321
279
|
}
|
|
322
280
|
export interface EntryPresentation {
|
|
323
281
|
readonly joinedBefore: boolean;
|
|
324
282
|
readonly scene: string;
|
|
325
283
|
readonly joinContext: string;
|
|
326
|
-
readonly recentActivities: readonly
|
|
327
|
-
readonly perceptions: Readonly<Record<number, Perception>>;
|
|
328
|
-
readonly sayNumbers: Readonly<Record<number, number>>;
|
|
329
|
-
}
|
|
330
|
-
export interface ListPresentation {
|
|
331
|
-
readonly context: readonly string[];
|
|
332
|
-
readonly participants: readonly string[];
|
|
333
|
-
readonly activities: number;
|
|
284
|
+
readonly recentActivities: readonly PerceivedActivity[];
|
|
334
285
|
}
|
|
335
286
|
export interface PresenceClassification {
|
|
336
287
|
readonly orderedHereNames: readonly string[];
|
|
337
288
|
readonly lingeringCount: number;
|
|
338
|
-
readonly classificationByName: Readonly<Record<string, 'here' | 'lingering' | 'done' | 'not-joined'>>;
|
|
289
|
+
readonly classificationByName: Readonly<Record<string, 'here' | 'lingering' | 'away' | 'done' | 'not-joined'>>;
|
|
339
290
|
}
|
|
340
291
|
export interface CatchPresence {
|
|
341
292
|
readonly participants: readonly ParticipantSummary[];
|
|
342
293
|
readonly now: number;
|
|
343
294
|
}
|
|
344
|
-
export interface StreamProjection {
|
|
345
|
-
readonly activities: readonly {
|
|
346
|
-
readonly activity: ArchiveRecord;
|
|
347
|
-
readonly route?: string;
|
|
348
|
-
}[];
|
|
349
|
-
readonly cursor: number;
|
|
350
|
-
readonly hasMore: boolean;
|
|
351
|
-
}
|
|
352
295
|
export interface SquareObservation {
|
|
353
296
|
readonly location: string;
|
|
354
297
|
readonly standingCount: number;
|
|
355
|
-
readonly held: boolean;
|
|
356
298
|
readonly now: number;
|
|
357
299
|
readonly participant?: string;
|
|
358
|
-
readonly automaticDeliveryAvailable: boolean;
|
|
359
300
|
}
|
|
360
301
|
export interface JoinObservation extends SquareObservation {
|
|
302
|
+
readonly deliveryAvailable: boolean;
|
|
361
303
|
readonly entry?: EntryPresentation;
|
|
362
304
|
/** The receipt succeeded, but optional recent-history/context enrichment was unavailable. */
|
|
363
305
|
readonly enrichmentUnavailable?: true;
|
|
364
306
|
}
|
|
365
|
-
export interface ExpressionObservation extends Pick<SquareObservation, 'standingCount' | '
|
|
307
|
+
export interface ExpressionObservation extends Pick<SquareObservation, 'standingCount' | 'now'> {
|
|
366
308
|
readonly ownActivityCount: number;
|
|
367
309
|
readonly hardCap: number | null;
|
|
368
310
|
}
|
|
311
|
+
export interface ExpressContext {
|
|
312
|
+
readonly activities: readonly {
|
|
313
|
+
readonly id: ActivityId;
|
|
314
|
+
readonly actor: string;
|
|
315
|
+
readonly preview: string;
|
|
316
|
+
readonly truncated: boolean;
|
|
317
|
+
}[];
|
|
318
|
+
/** Unread activities outside this preview, not a consumption cursor. */
|
|
319
|
+
readonly remaining: number;
|
|
320
|
+
}
|
|
369
321
|
export interface ExpressObservation extends SquareObservation, ExpressionObservation {
|
|
370
322
|
readonly participant: string;
|
|
371
|
-
readonly
|
|
372
|
-
readonly sayNumbers: Readonly<Record<number, number>>;
|
|
373
|
-
readonly perceptions: Readonly<Record<number, Perception>>;
|
|
323
|
+
readonly context: ExpressContext;
|
|
374
324
|
}
|
|
375
325
|
export interface CatchObservation extends SquareObservation, ExpressionObservation {
|
|
326
|
+
readonly deliveryAvailable: boolean;
|
|
376
327
|
readonly participant: string;
|
|
377
|
-
readonly sayNumbers: Readonly<Record<number, number>>;
|
|
378
328
|
readonly presence?: CatchPresence;
|
|
379
|
-
readonly stop?: CatchStopStatus;
|
|
380
|
-
readonly waitStarted?: boolean;
|
|
381
|
-
readonly replaced?: boolean;
|
|
382
329
|
readonly enrichmentUnavailable?: true;
|
|
383
330
|
}
|
|
384
331
|
export interface HistoryContinuation {
|
|
@@ -397,17 +344,12 @@ export interface HistoryObservation extends SquareObservation {
|
|
|
397
344
|
export interface ListenerObservation extends SquareObservation {
|
|
398
345
|
readonly participant: string;
|
|
399
346
|
readonly target: string;
|
|
400
|
-
readonly requestedTarget: string;
|
|
401
|
-
}
|
|
402
|
-
export interface LifecycleObservation extends SquareObservation {
|
|
403
|
-
readonly activity: ArchiveRecord;
|
|
404
347
|
}
|
|
405
|
-
export interface StatusObservation extends SquareObservation, PresenceClassification {
|
|
348
|
+
export interface StatusObservation extends Omit<SquareObservation, 'now'>, PresenceClassification {
|
|
406
349
|
readonly knownParticipantCount: number;
|
|
407
|
-
readonly latestActNumber?: number;
|
|
408
350
|
readonly latestPerception?: Perception;
|
|
409
351
|
readonly joins: readonly {
|
|
410
|
-
readonly activity:
|
|
352
|
+
readonly activity: Activity;
|
|
411
353
|
readonly rejoined: boolean;
|
|
412
354
|
}[];
|
|
413
355
|
readonly joinCount: number;
|
package/dist/square-index.d.ts
CHANGED
|
@@ -1,24 +1,16 @@
|
|
|
1
1
|
import { type VersionObserver } from './file-changes.js';
|
|
2
|
-
import { type
|
|
3
|
-
/** A rebuildable discovery hint: the
|
|
4
|
-
* It is deliberately not a participant, generation, ownership, suppression, or evidence row. */
|
|
2
|
+
import { type Owner } from './model.js';
|
|
3
|
+
/** A rebuildable discovery hint: the owner that may have used an artifact and its path. */
|
|
5
4
|
export interface SquareIndexEntry {
|
|
6
5
|
readonly location: string;
|
|
7
|
-
readonly
|
|
6
|
+
readonly owner: Owner;
|
|
8
7
|
}
|
|
9
8
|
export interface SquareIndexQuery {
|
|
10
|
-
readonly
|
|
11
|
-
readonly channel?: string;
|
|
12
|
-
readonly session?: string;
|
|
13
|
-
readonly agent?: string;
|
|
9
|
+
readonly owner?: Owner;
|
|
14
10
|
}
|
|
15
|
-
/** The index is the only host-side Square state. Its path is test/configuration plumbing, not authority. */
|
|
16
11
|
export declare function squareIndexPath(env?: NodeJS.ProcessEnv): string;
|
|
17
|
-
/** Read and verify the disposable index. Paths are canonicalized on access and duplicates collapse. */
|
|
18
12
|
export declare function indexedSquareEntries(query?: SquareIndexQuery, env?: NodeJS.ProcessEnv): Promise<readonly SquareIndexEntry[]>;
|
|
19
13
|
export declare function indexedSquarePaths(query?: SquareIndexQuery, env?: NodeJS.ProcessEnv): Promise<readonly string[]>;
|
|
20
14
|
/** Best-effort discovery only: a busy index skips publication, never queues or delays artifact work. */
|
|
21
|
-
export declare function rememberSquarePath(location: string,
|
|
22
|
-
/** Index loss is tolerated by every caller; this observer is only a wake hint for rediscovery. */
|
|
15
|
+
export declare function rememberSquarePath(location: string, owner: Owner, index: string): Promise<boolean>;
|
|
23
16
|
export declare function observeSquareIndex(env?: NodeJS.ProcessEnv): Promise<VersionObserver<string>>;
|
|
24
|
-
export declare function bodyKey(body: Body): string;
|
package/dist/square-index.js
CHANGED
|
@@ -5,8 +5,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
5
5
|
import { withFileLock } from './file-lock.js';
|
|
6
6
|
import { canonicalPath } from './canonical-path.js';
|
|
7
7
|
import { observeFileMetadata } from './file-changes.js';
|
|
8
|
-
import {
|
|
9
|
-
/** The index is the only host-side Square state. Its path is test/configuration plumbing, not authority. */
|
|
8
|
+
import { validateOwner } from './model.js';
|
|
10
9
|
export function squareIndexPath(env = process.env) {
|
|
11
10
|
const configured = env.SQUARE_INDEX?.trim();
|
|
12
11
|
return path.resolve(configured ?? path.join(os.homedir(), '.square', 'index.ndjsonl'));
|
|
@@ -18,24 +17,17 @@ function validEntry(value) {
|
|
|
18
17
|
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
19
18
|
return undefined;
|
|
20
19
|
const row = value;
|
|
21
|
-
if (typeof row.location !== 'string' || !path.isAbsolute(row.location)
|
|
22
|
-
|| row.body === null || typeof row.body !== 'object' || Array.isArray(row.body))
|
|
20
|
+
if (typeof row.location !== 'string' || !path.isAbsolute(row.location) || typeof row.owner !== 'string')
|
|
23
21
|
return undefined;
|
|
24
|
-
const raw = row.body;
|
|
25
|
-
const body = {
|
|
26
|
-
channel: raw.channel,
|
|
27
|
-
session: raw.session,
|
|
28
|
-
...(raw.agent === undefined ? {} : { agent: raw.agent }),
|
|
29
|
-
};
|
|
30
22
|
try {
|
|
31
|
-
|
|
23
|
+
validateOwner(row.owner);
|
|
32
24
|
}
|
|
33
25
|
catch {
|
|
34
26
|
return undefined;
|
|
35
27
|
}
|
|
36
|
-
if (Object.keys(
|
|
28
|
+
if (Object.keys(row).some((key) => !['location', 'owner'].includes(key)))
|
|
37
29
|
return undefined;
|
|
38
|
-
return { location: row.location,
|
|
30
|
+
return { location: row.location, owner: row.owner };
|
|
39
31
|
}
|
|
40
32
|
async function readEntries(index) {
|
|
41
33
|
let content;
|
|
@@ -56,25 +48,13 @@ async function readEntries(index) {
|
|
|
56
48
|
if (entry !== undefined)
|
|
57
49
|
entries.push(entry);
|
|
58
50
|
}
|
|
59
|
-
catch {
|
|
60
|
-
// The index is disposable discovery input. A malformed line is ignored rather than
|
|
61
|
-
// becoming a second authority or preventing access to valid entries beside it.
|
|
62
|
-
}
|
|
51
|
+
catch { /* Disposable discovery input. */ }
|
|
63
52
|
}
|
|
64
53
|
return entries;
|
|
65
54
|
}
|
|
66
55
|
function matches(entry, query) {
|
|
67
|
-
|
|
68
|
-
return false;
|
|
69
|
-
if (query.channel !== undefined && entry.body.channel !== query.channel)
|
|
70
|
-
return false;
|
|
71
|
-
if (query.session !== undefined && entry.body.session !== query.session)
|
|
72
|
-
return false;
|
|
73
|
-
if (query.agent !== undefined && entry.body.agent !== query.agent)
|
|
74
|
-
return false;
|
|
75
|
-
return true;
|
|
56
|
+
return query.owner === undefined || entry.owner === query.owner;
|
|
76
57
|
}
|
|
77
|
-
/** Read and verify the disposable index. Paths are canonicalized on access and duplicates collapse. */
|
|
78
58
|
export async function indexedSquareEntries(query = {}, env = process.env) {
|
|
79
59
|
const entries = await readEntries(squareIndexPath(env));
|
|
80
60
|
const result = [];
|
|
@@ -89,11 +69,11 @@ export async function indexedSquareEntries(query = {}, env = process.env) {
|
|
|
89
69
|
catch {
|
|
90
70
|
continue;
|
|
91
71
|
}
|
|
92
|
-
const key = JSON.stringify([location, entry.
|
|
72
|
+
const key = JSON.stringify([location, entry.owner]);
|
|
93
73
|
if (seen.has(key))
|
|
94
74
|
continue;
|
|
95
75
|
seen.add(key);
|
|
96
|
-
result.push({ location,
|
|
76
|
+
result.push({ location, owner: entry.owner });
|
|
97
77
|
}
|
|
98
78
|
return result;
|
|
99
79
|
}
|
|
@@ -101,15 +81,15 @@ export async function indexedSquarePaths(query = {}, env = process.env) {
|
|
|
101
81
|
return [...new Set((await indexedSquareEntries(query, env)).map((entry) => entry.location))];
|
|
102
82
|
}
|
|
103
83
|
/** Best-effort discovery only: a busy index skips publication, never queues or delays artifact work. */
|
|
104
|
-
export async function rememberSquarePath(location,
|
|
105
|
-
|
|
84
|
+
export async function rememberSquarePath(location, owner, index) {
|
|
85
|
+
validateOwner(owner);
|
|
106
86
|
const canonical = await canonicalPath(location);
|
|
107
87
|
await fs.mkdir(path.dirname(index), { recursive: true });
|
|
108
88
|
return await withFileLock(lockPath(index), { retryMs: 10, skipIfBusy: true }, async () => {
|
|
109
89
|
const entries = await readEntries(index);
|
|
110
|
-
if (entries.some((entry) => entry.location === canonical &&
|
|
90
|
+
if (entries.some((entry) => entry.location === canonical && entry.owner === owner))
|
|
111
91
|
return false;
|
|
112
|
-
const next = [...entries, { location: canonical,
|
|
92
|
+
const next = [...entries, { location: canonical, owner }];
|
|
113
93
|
const temporary = `${index}.${process.pid}.${randomUUID()}.tmp`;
|
|
114
94
|
try {
|
|
115
95
|
await fs.writeFile(temporary, next.map((entry) => JSON.stringify(entry)).join('\n') + '\n', { flag: 'wx', mode: 0o600 });
|
|
@@ -121,11 +101,6 @@ export async function rememberSquarePath(location, body, index) {
|
|
|
121
101
|
return true;
|
|
122
102
|
}) ?? false;
|
|
123
103
|
}
|
|
124
|
-
/** Index loss is tolerated by every caller; this observer is only a wake hint for rediscovery. */
|
|
125
104
|
export function observeSquareIndex(env = process.env) {
|
|
126
105
|
return observeFileMetadata(squareIndexPath(env));
|
|
127
106
|
}
|
|
128
|
-
export function bodyKey(body) {
|
|
129
|
-
validateBody(body);
|
|
130
|
-
return JSON.stringify([body.channel, body.session, body.agent ?? null]);
|
|
131
|
-
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { DatabaseSync } from 'node:sqlite';
|
|
2
2
|
export declare const FORMAT4_ADDED_OBJECTS: ReadonlySet<string>;
|
|
3
3
|
export declare const FORMAT4_REPLACED_OBJECTS: ReadonlySet<string>;
|
|
4
|
+
/** Historical activity framing only; no runtime activity decoder accepts these kinds. */
|
|
5
|
+
export declare function legacyActivitySchema(currentSchemaSql: string): string;
|
|
4
6
|
/**
|
|
5
7
|
* Read-only recognition of the exact row framing accepted by explicit migration.
|
|
6
8
|
* It inspects identity, owned schema and journal mode only; it never reads row
|
|
@@ -15,6 +15,14 @@ CREATE INDEX route_session ON routes(session_json, position);`;
|
|
|
15
15
|
function normalizedSql(sql) {
|
|
16
16
|
return sql.trim().replace(/;$/, '').replace(/\s+/g, ' ').replace(/\s*([(),=])\s*/g, '$1');
|
|
17
17
|
}
|
|
18
|
+
/** Historical activity framing only; no runtime activity decoder accepts these kinds. */
|
|
19
|
+
export function legacyActivitySchema(currentSchemaSql) {
|
|
20
|
+
return currentSchemaSql
|
|
21
|
+
.replace("kind IN ('join','done','say','read','listen','ignore','unlisten')", "kind IN ('join','done','say','hold','resume','read','listen','ignore','unlisten')")
|
|
22
|
+
.replace(' actor_json TEXT COLLATE BINARY NOT NULL,', ' actor_json TEXT COLLATE BINARY,')
|
|
23
|
+
.replace(' actor_key_json TEXT COLLATE BINARY NOT NULL,', ' actor_key_json TEXT COLLATE BINARY,')
|
|
24
|
+
.replace(" CHECK (kind = 'say' OR (reach IS NULL AND reply_index IS NULL))", " CHECK ((actor_json IS NULL) = (actor_key_json IS NULL)),\n CHECK (kind IN ('hold','resume') OR actor_json IS NOT NULL),\n CHECK (kind = 'say' OR (reach IS NULL AND reply_index IS NULL))");
|
|
25
|
+
}
|
|
18
26
|
function legacyRowSchema(currentSchemaSql, version) {
|
|
19
27
|
const statements = currentSchemaSql.split(';').map((statement) => statement.trim()).filter(Boolean)
|
|
20
28
|
.filter((statement) => {
|
|
@@ -1,15 +1,2 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { SquareBackends } from './square-backend.js';
|
|
3
|
-
export type ParsedSquareLocation = {
|
|
4
|
-
readonly kind: 'local';
|
|
5
|
-
readonly path: string;
|
|
6
|
-
} | {
|
|
7
|
-
readonly kind: 'stdio';
|
|
8
|
-
readonly backend: string;
|
|
9
|
-
readonly key: string;
|
|
10
|
-
readonly uri: string;
|
|
11
|
-
};
|
|
12
|
-
export declare function parseSquareLocation(value: string): ParsedSquareLocation;
|
|
1
|
+
import type { SquareLocation } from './square-facade.js';
|
|
13
2
|
export declare function squareLocation(input: SquareLocation): string;
|
|
14
|
-
export declare function localSquareLocation(input: SquareLocation): string;
|
|
15
|
-
export declare function backendConfiguration(input: SquareAtInput): Promise<SquareBackends>;
|
package/dist/square-location.js
CHANGED
|
@@ -1,77 +1,11 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import { readFile } from 'node:fs/promises';
|
|
4
2
|
import { SquareError } from './model.js';
|
|
5
|
-
export function parseSquareLocation(value) {
|
|
6
|
-
// Windows roots include drive-relative prefixes such as D:.
|
|
7
|
-
if (path.win32.parse(value).root !== '' || !/^[A-Za-z][A-Za-z0-9+.-]*:/.test(value))
|
|
8
|
-
return { kind: 'local', path: value };
|
|
9
|
-
// Parse the raw components, before URL dot-segment normalization or host lowercasing.
|
|
10
|
-
const match = /^stdio:\/\/([A-Za-z0-9_-]+)\/([^/?#]+)$/.exec(value);
|
|
11
|
-
if (!match)
|
|
12
|
-
throw new SquareError('invalid_args', 'Expected a local path or stdio://<configured-name>/<encoded-square-key>; no credentials, port, query or fragment');
|
|
13
|
-
let key;
|
|
14
|
-
try {
|
|
15
|
-
key = decodeURIComponent(match[2]);
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
throw new SquareError('invalid_args', 'Invalid percent encoding in stdio square key');
|
|
19
|
-
}
|
|
20
|
-
if (!key || key === '.' || key === '..' || /[/\\\x00-\x1f\x7f]/.test(key))
|
|
21
|
-
throw new SquareError('invalid_args', 'A stdio square key must be one nonempty name, not a path');
|
|
22
|
-
return { kind: 'stdio', backend: match[1], key, uri: `stdio://${match[1]}/${encodeURIComponent(key)}` };
|
|
23
|
-
}
|
|
24
3
|
export function squareLocation(input) {
|
|
25
4
|
const selected = input.path ?? (input.env ?? process.env).SQUARE_LOCATION;
|
|
26
5
|
if (selected === undefined || selected.trim() === '')
|
|
27
6
|
throw new SquareError('invalid_args', 'Square needs a location');
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const location = squareLocation(input);
|
|
33
|
-
if (parseSquareLocation(location).kind !== 'local')
|
|
34
|
-
throw new SquareError('invalid_args', 'This file operation requires a local square artifact; backend URIs are not supported');
|
|
35
|
-
return location;
|
|
36
|
-
}
|
|
37
|
-
function validateBackends(value) {
|
|
38
|
-
const object = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
39
|
-
const invalid = () => new SquareError('invalid_args', 'Invalid backend configuration: expected {backends:{name:{command,args?,cwd?,env?}}}');
|
|
40
|
-
if (!object(value))
|
|
41
|
-
throw invalid();
|
|
42
|
-
for (const [name, entry] of Object.entries(value)) {
|
|
43
|
-
if (!/^[A-Za-z0-9_-]+$/.test(name) || !object(entry) || typeof entry.command !== 'string' || !entry.command.trim()
|
|
44
|
-
|| Object.keys(entry).some(key => !['command', 'args', 'cwd', 'env'].includes(key))
|
|
45
|
-
|| (entry.args !== undefined && (!Array.isArray(entry.args) || entry.args.some(arg => typeof arg !== 'string')))
|
|
46
|
-
|| (entry.cwd !== undefined && typeof entry.cwd !== 'string')
|
|
47
|
-
|| (entry.env !== undefined && (!object(entry.env) || Object.values(entry.env).some(v => typeof v !== 'string'))))
|
|
48
|
-
throw invalid();
|
|
49
|
-
}
|
|
50
|
-
return value;
|
|
51
|
-
}
|
|
52
|
-
export async function backendConfiguration(input) {
|
|
53
|
-
if (input.backends !== undefined)
|
|
54
|
-
return validateBackends(input.backends);
|
|
55
|
-
const env = input.env ?? process.env;
|
|
56
|
-
const configured = env.SQUARE_BACKENDS_CONFIG;
|
|
57
|
-
const file = configured === undefined ? path.join(env.HOME ?? os.homedir(), '.config/square/backends.json')
|
|
58
|
-
: path.resolve(input.cwd ?? process.cwd(), configured);
|
|
59
|
-
let text;
|
|
60
|
-
try {
|
|
61
|
-
text = await readFile(file, 'utf8');
|
|
62
|
-
}
|
|
63
|
-
catch (error) {
|
|
64
|
-
if (configured === undefined && error.code === 'ENOENT')
|
|
65
|
-
return {};
|
|
66
|
-
throw new SquareError('invalid_args', 'Cannot read backend configuration; check SQUARE_BACKENDS_CONFIG');
|
|
67
|
-
}
|
|
68
|
-
try {
|
|
69
|
-
const parsed = JSON.parse(text);
|
|
70
|
-
if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object' || Object.keys(parsed).some(key => key !== 'backends') || parsed.backends === undefined)
|
|
71
|
-
throw new Error();
|
|
72
|
-
return validateBackends(parsed.backends);
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
throw new SquareError('invalid_args', 'Invalid backend configuration; expected {backends:{name:{command,args?,cwd?,env?}}}');
|
|
76
|
-
}
|
|
7
|
+
// Windows roots include drive-relative prefixes such as D:.
|
|
8
|
+
if (path.win32.parse(selected).root === '' && /^[A-Za-z][A-Za-z0-9+.-]*:/.test(selected))
|
|
9
|
+
throw new SquareError('invalid_args', 'Expected a local square artifact path');
|
|
10
|
+
return path.resolve(input.cwd ?? process.cwd(), selected);
|
|
77
11
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type HarnessCoordinate } from './owner.js';
|
|
2
2
|
/** Offline conversion input only; never a runtime ownership or evidence reader. */
|
|
3
3
|
export interface LegacyOwner {
|
|
4
4
|
readonly participant: string;
|
|
5
|
-
readonly
|
|
5
|
+
readonly coordinate: HarnessCoordinate;
|
|
6
6
|
}
|
|
7
7
|
export interface LegacyHostData {
|
|
8
8
|
readonly owners: readonly LegacyOwner[];
|
|
@@ -5,7 +5,8 @@ import path from 'node:path';
|
|
|
5
5
|
import { pathToFileURL } from 'node:url';
|
|
6
6
|
import { canonicalPath } from './canonical-path.js';
|
|
7
7
|
import { inSqliteTransaction } from './sqlite-transaction.js';
|
|
8
|
-
import {
|
|
8
|
+
import { harnessOwner } from './owner.js';
|
|
9
|
+
import { nameKey } from './model.js';
|
|
9
10
|
const LEDGER_FILENAME = 'host-ledger.sqlite';
|
|
10
11
|
const LEDGER_APPLICATION_ID = 0x5351484c;
|
|
11
12
|
const LEDGER_VERSION = 2;
|
|
@@ -144,11 +145,11 @@ export async function readLegacyHostData(location, env = process.env, signal) {
|
|
|
144
145
|
if (value.v !== 1 || value.location !== canonical || typeof value.participant !== 'string'
|
|
145
146
|
|| !finite(at))
|
|
146
147
|
throw invalid('owner fields are malformed.');
|
|
147
|
-
const
|
|
148
|
-
|
|
148
|
+
const coordinate = { channel: value.channel, session: value.session, ...(value.agent === undefined ? {} : { agent: value.agent }) };
|
|
149
|
+
harnessOwner(coordinate);
|
|
149
150
|
projections(row, { location_key: recordKey(canonical), participant_key: recordKey(nameKey(value.participant)),
|
|
150
|
-
channel_key: recordKey(
|
|
151
|
-
return { participant: value.participant,
|
|
151
|
+
channel_key: recordKey(coordinate.channel), session_key: recordKey(coordinate.session), effective_at: at });
|
|
152
|
+
return { participant: value.participant, coordinate };
|
|
152
153
|
});
|
|
153
154
|
const evidence = database.prepare("SELECT * FROM host_evidence WHERE location_key=? AND kind IN ('wake','presentation') ORDER BY seq").all(recordKey(canonical)).map(row => {
|
|
154
155
|
const value = decodeEvidence(record(row));
|
|
@@ -20,7 +20,10 @@ export interface MigrationOutcome {
|
|
|
20
20
|
/**
|
|
21
21
|
* Explicit conversion of the supported legacy formats. Row-backed versions 2 and 3 are
|
|
22
22
|
* preflighted read-only, then receive ownership/evidence and deferral storage in one
|
|
23
|
-
* format-
|
|
23
|
+
* format-8 transaction. Formats 4/5 gain attachment; format 4 also gains empty deferrals.
|
|
24
|
+
* All legacy seats are encoded as opaque owners, preserving format-6 attachment.
|
|
25
|
+
* Every source drops obsolete activities and their identity references, clears dangling
|
|
26
|
+
* replies and rebuilds derived indices; surviving ids and allocation watermarks remain stable.
|
|
24
27
|
* Legacy delivery evidence is copied from HostLedger only when its owner/session mapping is
|
|
25
28
|
* unique; no source record is retired. Version 1 is not a supported source for this format transition.
|
|
26
29
|
*/
|