@astrosheep/square 0.3.31 → 0.3.33
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/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity.js +7 -6
- package/dist/artifact.js +4 -2
- package/dist/automatic-session.js +19 -1
- package/dist/claude-hook.d.ts +2 -1
- package/dist/claude-hook.js +6 -2
- package/dist/cli/square-commands.js +4 -5
- package/dist/codex-hook.d.ts +2 -1
- package/dist/codex-hook.js +6 -2
- package/dist/codex-queue.d.ts +1 -1
- package/dist/codex-queue.js +9 -3
- package/dist/delivery-operations.js +112 -16
- package/dist/delivery.d.ts +2 -1
- package/dist/host-ledger-file-adapter.js +2 -11
- package/dist/model.d.ts +3 -0
- package/dist/model.js +2 -2
- package/dist/notifications.d.ts +7 -0
- package/dist/notifications.js +84 -16
- package/dist/open-square.d.ts +2 -1
- package/dist/paseo-delivery.d.ts +1 -1
- package/dist/paseo-delivery.js +23 -4
- package/dist/paseo-state.d.ts +1 -1
- package/dist/paseo-state.js +2 -2
- package/dist/ports.d.ts +11 -0
- package/dist/presence.js +1 -1
- package/dist/registry.js +9 -10
- package/dist/routes.d.ts +25 -1
- package/dist/routes.js +107 -6
- package/dist/square-actions.d.ts +1 -0
- package/dist/square-actions.js +38 -1
- package/dist/square-facade.d.ts +4 -1
- package/dist/square-file-adapter.d.ts +2 -1
- package/dist/square-file-adapter.js +2 -1
- package/dist/square-projections.js +10 -5
- package/dist/wake-port.js +1 -1
- package/package.json +1 -1
package/dist/notifications.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
3
4
|
import { planActNotifications, } from './delivery.js';
|
|
4
5
|
import { SquareError } from './model.js';
|
|
5
6
|
import { SLEEP_MS, matchesMentionTarget } from './runtime.js';
|
|
@@ -13,6 +14,7 @@ import { projectPresentationEvidence } from './square-projections.js';
|
|
|
13
14
|
import { createHostLedgerPort } from './host-ledger-file-adapter.js';
|
|
14
15
|
export { planActNotifications, matchesMentionTarget };
|
|
15
16
|
export { notificationMessageId } from './delivery.js';
|
|
17
|
+
export const PRIVILEGED_HOOK_BUDGET_MS = 3000;
|
|
16
18
|
export function wakeGraceMs(env = process.env) {
|
|
17
19
|
const value = Number.parseInt(env.SQUARE_NOTIFY_DELIVERY_WAIT_MS ?? '5000', 10);
|
|
18
20
|
if (!Number.isFinite(value) || value <= 0) {
|
|
@@ -77,7 +79,7 @@ export async function waitForDeliveredNotification(squarePath, name, ref, opts =
|
|
|
77
79
|
}
|
|
78
80
|
return false;
|
|
79
81
|
}
|
|
80
|
-
async function defaultWakeAdapters() {
|
|
82
|
+
export async function defaultWakeAdapters() {
|
|
81
83
|
const adapters = [];
|
|
82
84
|
try {
|
|
83
85
|
const { CodexQueueAdapter } = await import('./codex-queue.js');
|
|
@@ -95,20 +97,38 @@ async function defaultWakeAdapters() {
|
|
|
95
97
|
}
|
|
96
98
|
return adapters;
|
|
97
99
|
}
|
|
98
|
-
function
|
|
100
|
+
export async function createDefaultWakeTransport(hostLedger, clock, env = process.env) {
|
|
101
|
+
const adapters = await defaultWakeAdapters();
|
|
102
|
+
return createWakeTransport(env.SQUARE_DISABLE_PASEO_WAKE === '1' ? adapters.filter((adapter) => adapter.kind !== 'paseo') : adapters, hostLedger, clock);
|
|
103
|
+
}
|
|
104
|
+
export function createWakeTransport(adapters, hostLedger, clock) {
|
|
99
105
|
return {
|
|
100
|
-
|
|
106
|
+
probe: async (route) => {
|
|
107
|
+
const adapter = adapters.find((candidate) => candidate.kind === route.kind);
|
|
108
|
+
if (adapter === undefined)
|
|
109
|
+
return { outcome: 'not-capable', diagnostic: `no adapter for ${route.kind}` };
|
|
110
|
+
const probe = adapter.probe;
|
|
111
|
+
if (probe === undefined)
|
|
112
|
+
return true;
|
|
113
|
+
try {
|
|
114
|
+
return await probe.call(adapter, route.address);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
return { outcome: 'not-capable', diagnostic: error instanceof Error ? error.message : String(error) };
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
attempt: async (request, timeoutMs) => {
|
|
101
121
|
const adapter = adapters.find((candidate) => candidate.kind === request.route.kind);
|
|
102
122
|
if (adapter === undefined)
|
|
103
|
-
return { outcome: '
|
|
123
|
+
return { outcome: 'not-capable', diagnostic: `no adapter for ${request.route.kind}` };
|
|
104
124
|
try {
|
|
105
|
-
const result = await adapter.dispatch(request.route.address, renderWakePayload(request), async () => true);
|
|
125
|
+
const result = await adapter.dispatch(request.route.address, renderWakePayload(request), async () => true, timeoutMs);
|
|
106
126
|
if (result.outcome === 'accepted')
|
|
107
127
|
return { outcome: 'accepted' };
|
|
108
128
|
if (result.outcome === 'failed')
|
|
109
129
|
return { outcome: 'failed', message: result.message };
|
|
110
130
|
if (result.outcome === 'unavailable')
|
|
111
|
-
return { outcome: 'failed', message: result.message, unavailable: true };
|
|
131
|
+
return { outcome: 'failed', message: result.message, unavailable: true, ...(result.retainRoute === true ? { retainRoute: true } : {}), ...(result.routeStale === true ? { routeStale: true } : {}) };
|
|
112
132
|
if (result.outcome === 'unknown')
|
|
113
133
|
return { outcome: 'unknown', diagnostic: result.message };
|
|
114
134
|
return { outcome: 'unknown', diagnostic: 'wake dispatch cancelled' };
|
|
@@ -117,15 +137,6 @@ function createWakeTransport(adapters, hostLedger, clock) {
|
|
|
117
137
|
return { outcome: 'unknown', diagnostic: error instanceof Error ? error.message : String(error) };
|
|
118
138
|
}
|
|
119
139
|
},
|
|
120
|
-
invalidate: async (request) => {
|
|
121
|
-
await hostLedger.ensurePresence({
|
|
122
|
-
location: request.route.location,
|
|
123
|
-
participant: request.route.participant,
|
|
124
|
-
session: request.route.sessionId,
|
|
125
|
-
channel: request.route.channel,
|
|
126
|
-
updatedAt: clock(),
|
|
127
|
-
}, 'user');
|
|
128
|
-
},
|
|
129
140
|
};
|
|
130
141
|
}
|
|
131
142
|
export async function processActNotificationsOnce(squarePath, actIndex, opts = {}) {
|
|
@@ -140,12 +151,69 @@ export async function processActNotificationsOnce(squarePath, actIndex, opts = {
|
|
|
140
151
|
try {
|
|
141
152
|
const adapters = opts.adapters ?? await defaultWakeAdapters();
|
|
142
153
|
const transport = createWakeTransport(adapters, hostLedger, now);
|
|
143
|
-
|
|
154
|
+
try {
|
|
155
|
+
return await deliverPending({ artifact: square.artifact, hostLedger, transport, location: squarePath, activity: actIndex, timeoutMs: Number(env.SQUARE_NOTIFY_DELIVERY_WAIT_MS ?? 5000), now: now() });
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return { attempted: 0, accepted: 0, failed: 0, unknown: 0, notCapable: 1 };
|
|
159
|
+
}
|
|
144
160
|
}
|
|
145
161
|
finally {
|
|
146
162
|
await closeOpenSquare(square);
|
|
147
163
|
}
|
|
148
164
|
}
|
|
165
|
+
/** Privileged hook fallback: sweep indexed squares plus the current cwd's local squares within one boundary budget. */
|
|
166
|
+
export async function sweepPrivilegedPending(cwd, env = process.env, suppliedAdapters, deadline = Date.now() + PRIVILEGED_HOOK_BUDGET_MS) {
|
|
167
|
+
const remainingMs = () => Math.max(0, deadline - Date.now());
|
|
168
|
+
if (remainingMs() === 0)
|
|
169
|
+
return;
|
|
170
|
+
const root = env.SQUARE_REGISTRY === undefined ? undefined : path.dirname(env.SQUARE_REGISTRY);
|
|
171
|
+
const hostLedger = createHostLedgerPort({ userPath: env.SQUARE_HOST_LEDGER_USER ?? root, localPath: env.SQUARE_HOST_LEDGER_LOCAL ?? root, readableScopes: ['user'], writableScope: 'user' });
|
|
172
|
+
let indexed = [];
|
|
173
|
+
try {
|
|
174
|
+
indexed = await hostLedger.listPresence({ scopes: ['user'], now: Date.now() });
|
|
175
|
+
}
|
|
176
|
+
catch { /* capability is best effort */ }
|
|
177
|
+
const paths = new Set(indexed.map((binding) => binding.location));
|
|
178
|
+
try {
|
|
179
|
+
for (const entry of await fs.promises.readdir(path.join(cwd, '.square'))) {
|
|
180
|
+
if (entry.endsWith('.square'))
|
|
181
|
+
paths.add(path.join(cwd, '.square', entry));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch { /* no local square directory */ }
|
|
185
|
+
if (remainingMs() === 0)
|
|
186
|
+
return;
|
|
187
|
+
const adapters = suppliedAdapters ?? await defaultWakeAdapters();
|
|
188
|
+
for (const squarePath of paths) {
|
|
189
|
+
if (remainingMs() === 0)
|
|
190
|
+
break;
|
|
191
|
+
try {
|
|
192
|
+
const square = await openSquare(squarePath, { hostLedger, env });
|
|
193
|
+
try {
|
|
194
|
+
await hostLedger.reconcileBinding({ artifact: square.artifact, scopes: ['user'], now: Date.now() }).catch(() => undefined);
|
|
195
|
+
if (remainingMs() === 0)
|
|
196
|
+
break;
|
|
197
|
+
const limit = Number.parseInt(env.SQUARE_NOTIFY_SWEEP_LIMIT ?? '8', 10);
|
|
198
|
+
const graceMs = 0;
|
|
199
|
+
const selected = await sweepPending({ artifact: square.artifact, hostLedger, location: squarePath, now: Date.now(), graceMs, limit: Number.isFinite(limit) && limit > 0 ? limit : 8 }).catch(() => []);
|
|
200
|
+
const transport = createWakeTransport(adapters, hostLedger, Date.now);
|
|
201
|
+
for (const actIndex of selected) {
|
|
202
|
+
const remaining = remainingMs();
|
|
203
|
+
if (remaining === 0)
|
|
204
|
+
break;
|
|
205
|
+
const configured = Number(env.SQUARE_NOTIFY_DELIVERY_WAIT_MS ?? 5000);
|
|
206
|
+
const timeoutMs = Math.max(1, Math.min(Number.isFinite(configured) && configured > 0 ? configured : 5000, remaining));
|
|
207
|
+
await deliverPending({ artifact: square.artifact, hostLedger, transport, location: squarePath, activity: actIndex, timeoutMs, now: Date.now() }).catch(() => undefined);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
await closeOpenSquare(square);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch { /* stale index entries are ignored by the hook */ }
|
|
215
|
+
}
|
|
216
|
+
}
|
|
149
217
|
/** Select sweep candidates from one frozen snapshot and one delivery replay. */
|
|
150
218
|
export async function pendingNotificationSweepFromState(squarePath, state, now, env, limit, deriveDelivery) {
|
|
151
219
|
const ledger = createHostLedgerPort({ userPath: env.SQUARE_HOST_LEDGER_USER, writableScope: 'user', readableScopes: ['user'] });
|
package/dist/open-square.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { HostLedgerPort, SquareArtifactPort } from './ports.js';
|
|
1
|
+
import type { HostLedgerPort, SquareArtifactPort, WakeTransportPort } from './ports.js';
|
|
2
2
|
/** Private binding assembled by storage and consumed by the four concerns. */
|
|
3
3
|
export interface OpenSquare {
|
|
4
4
|
readonly artifact: SquareArtifactPort;
|
|
5
5
|
readonly clock: () => number;
|
|
6
6
|
readonly location: string;
|
|
7
7
|
readonly hostLedger?: HostLedgerPort;
|
|
8
|
+
readonly wakeTransport?: WakeTransportPort;
|
|
8
9
|
readonly env?: NodeJS.ProcessEnv;
|
|
9
10
|
}
|
|
10
11
|
/** Package-private lifecycle boundary for bound squares. */
|
package/dist/paseo-delivery.d.ts
CHANGED
|
@@ -10,5 +10,5 @@ export declare class PaseoAdapter implements WakeAdapter {
|
|
|
10
10
|
private readonly opts;
|
|
11
11
|
readonly kind: "paseo";
|
|
12
12
|
constructor(opts?: PaseoAdapterOptions);
|
|
13
|
-
dispatch(address: Readonly<Record<string, string>>, payload: string, beforeSend: () => Promise<boolean
|
|
13
|
+
dispatch(address: Readonly<Record<string, string>>, payload: string, beforeSend: () => Promise<boolean>, timeoutMs?: number): Promise<WakeDispatchResult>;
|
|
14
14
|
}
|
package/dist/paseo-delivery.js
CHANGED
|
@@ -30,17 +30,29 @@ export class PaseoAdapter {
|
|
|
30
30
|
constructor(opts = {}) {
|
|
31
31
|
this.opts = opts;
|
|
32
32
|
}
|
|
33
|
-
async dispatch(address, payload, beforeSend) {
|
|
33
|
+
async dispatch(address, payload, beforeSend, timeoutMs = 5000) {
|
|
34
|
+
const deadline = Date.now() + timeoutMs;
|
|
35
|
+
const remainingMs = () => Math.max(0, deadline - Date.now());
|
|
36
|
+
const budgetUnavailable = () => ({
|
|
37
|
+
outcome: 'unavailable',
|
|
38
|
+
signature: 'dispatch_budget_exhausted',
|
|
39
|
+
message: 'The wake dispatch budget elapsed before Paseo accepted the wake.',
|
|
40
|
+
retainRoute: true,
|
|
41
|
+
});
|
|
34
42
|
const agentId = address.agentId?.trim();
|
|
35
43
|
if (!agentId) {
|
|
36
44
|
return {
|
|
37
45
|
outcome: 'unavailable',
|
|
38
46
|
signature: 'invalid_address',
|
|
39
47
|
message: 'Paseo route has no agent id.',
|
|
48
|
+
routeStale: true,
|
|
40
49
|
diagnostic: diagnostic('selection', address, 'invalid_address'),
|
|
41
50
|
};
|
|
42
51
|
}
|
|
43
|
-
|
|
52
|
+
let remaining = remainingMs();
|
|
53
|
+
if (remaining === 0)
|
|
54
|
+
return budgetUnavailable();
|
|
55
|
+
const discovery = (this.opts.discover ?? discoverPaseoAgents)(remaining);
|
|
44
56
|
if (discovery.error && discovery.agents.length === 0) {
|
|
45
57
|
return {
|
|
46
58
|
outcome: 'unavailable',
|
|
@@ -58,9 +70,13 @@ export class PaseoAdapter {
|
|
|
58
70
|
message: agent === undefined ? 'The registered Paseo agent was not found.' : 'The registered Paseo agent is not idle.',
|
|
59
71
|
diagnostic: diagnostic('selection', address, agent === undefined ? 'not_found' : 'not_idle'),
|
|
60
72
|
...(agent === undefined ? {} : { retainRoute: true }),
|
|
73
|
+
...(agent === undefined ? { routeStale: true } : {}),
|
|
61
74
|
};
|
|
62
75
|
}
|
|
63
|
-
|
|
76
|
+
remaining = remainingMs();
|
|
77
|
+
if (remaining === 0)
|
|
78
|
+
return budgetUnavailable();
|
|
79
|
+
if (!(await (this.opts.waitForBoundary ?? waitForPaseoWakeBoundary)(agent, remaining))) {
|
|
64
80
|
return {
|
|
65
81
|
outcome: 'unavailable',
|
|
66
82
|
signature: 'boundary_unavailable',
|
|
@@ -71,8 +87,11 @@ export class PaseoAdapter {
|
|
|
71
87
|
}
|
|
72
88
|
if (!(await beforeSend()))
|
|
73
89
|
return { outcome: 'cancelled' };
|
|
90
|
+
remaining = remainingMs();
|
|
91
|
+
if (remaining === 0)
|
|
92
|
+
return budgetUnavailable();
|
|
74
93
|
try {
|
|
75
|
-
(this.opts.sendWake ?? sendPaseoWake)({ agentId, prompt: payload });
|
|
94
|
+
(this.opts.sendWake ?? sendPaseoWake)({ agentId, prompt: payload }, { timeoutMs: remaining });
|
|
76
95
|
return { outcome: 'accepted' };
|
|
77
96
|
}
|
|
78
97
|
catch (error) {
|
package/dist/paseo-state.d.ts
CHANGED
|
@@ -8,4 +8,4 @@ export declare function discoverPaseoAgents(timeoutMs?: number): {
|
|
|
8
8
|
agents: PaseoAgent[];
|
|
9
9
|
error?: string;
|
|
10
10
|
};
|
|
11
|
-
export declare function waitForPaseoWakeBoundary(agent: Pick<PaseoAgent, 'id' | 'status'
|
|
11
|
+
export declare function waitForPaseoWakeBoundary(agent: Pick<PaseoAgent, 'id' | 'status'>, timeoutMs?: number): Promise<boolean>;
|
package/dist/paseo-state.js
CHANGED
|
@@ -22,10 +22,10 @@ export function discoverPaseoAgents(timeoutMs = 5000) {
|
|
|
22
22
|
return { agents: [], error: error instanceof Error ? error.message : String(error) };
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
export async function waitForPaseoWakeBoundary(agent) {
|
|
25
|
+
export async function waitForPaseoWakeBoundary(agent, timeoutMs = 30_000) {
|
|
26
26
|
if (agent.status === 'idle')
|
|
27
27
|
return true;
|
|
28
28
|
if (agent.status !== 'running')
|
|
29
29
|
return false;
|
|
30
|
-
return waitForPaseoToolBoundary(agent.id);
|
|
30
|
+
return waitForPaseoToolBoundary(agent.id, { timeoutMs });
|
|
31
31
|
}
|
package/dist/ports.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ export interface SquareArtifactPort {
|
|
|
17
17
|
}
|
|
18
18
|
/** Capability-neutral wake transport. Unused by this Contract's activity operations. */
|
|
19
19
|
export interface WakeTransportPort {
|
|
20
|
+
/** Capability check that performs no external wake and writes no evidence. */
|
|
21
|
+
probe?(route: WakeRoute): Promise<boolean | {
|
|
22
|
+
readonly outcome: 'not-capable';
|
|
23
|
+
readonly diagnostic?: string;
|
|
24
|
+
}>;
|
|
20
25
|
attempt(request: WakeRequest, timeoutMs: number): Promise<WakeOutcome>;
|
|
21
26
|
/** Optional route retirement supplied by the concrete executor adapter. */
|
|
22
27
|
invalidate?(request: WakeRequest): Promise<void>;
|
|
@@ -65,6 +70,11 @@ export type WakeOutcome = {
|
|
|
65
70
|
readonly message?: string;
|
|
66
71
|
readonly attemptN?: number;
|
|
67
72
|
readonly unavailable?: boolean;
|
|
73
|
+
readonly retainRoute?: boolean;
|
|
74
|
+
readonly routeStale?: boolean;
|
|
75
|
+
} | {
|
|
76
|
+
readonly outcome: 'not-capable';
|
|
77
|
+
readonly diagnostic?: string;
|
|
68
78
|
} | {
|
|
69
79
|
readonly outcome: 'unknown';
|
|
70
80
|
readonly diagnostic?: string;
|
|
@@ -98,6 +108,7 @@ export interface DeliveryResult {
|
|
|
98
108
|
readonly accepted: number;
|
|
99
109
|
readonly failed: number;
|
|
100
110
|
readonly unknown: number;
|
|
111
|
+
readonly notCapable: number;
|
|
101
112
|
}
|
|
102
113
|
export interface DeliverPendingInput {
|
|
103
114
|
readonly artifact: SquareArtifactPort;
|
package/dist/presence.js
CHANGED
|
@@ -5,7 +5,7 @@ import { recordObservation } from './runtime.js';
|
|
|
5
5
|
import { catchUp as actionCatchUp } from './square-actions.js';
|
|
6
6
|
function operationContext(square) {
|
|
7
7
|
return 'artifact' in square
|
|
8
|
-
? { artifact: square.artifact, clock: square.clock }
|
|
8
|
+
? { artifact: square.artifact, clock: square.clock, location: square.location, hostLedger: square.hostLedger, wakeTransport: square.wakeTransport, env: square.env }
|
|
9
9
|
: { artifact: square.cell, clock: square.clock };
|
|
10
10
|
}
|
|
11
11
|
export async function catchUp(square, name, options = {}, deriveDelivery = deriveDeliveryModel) {
|
package/dist/registry.js
CHANGED
|
@@ -26,7 +26,7 @@ async function writePresence(sessionId, name, squarePath, options, done, scope =
|
|
|
26
26
|
return; const env = options.env ?? process.env; const channel = options.channel ?? 'unknown'; const port = ledger(env, scope); const location = await canonicalSquarePath(squarePath); if (done)
|
|
27
27
|
await port.removePresence({ location, participant: name, session: sessionId, channel });
|
|
28
28
|
else
|
|
29
|
-
await port.ensurePresence({ location, participant: name, session: sessionId, channel,
|
|
29
|
+
await port.ensurePresence({ location, participant: name, session: sessionId, channel, updatedAt: options.at ?? Date.now() }); }
|
|
30
30
|
export function recordJoin(sessionId, name, squarePath, options = {}) { return writePresence(sessionId, name, squarePath, options, false); }
|
|
31
31
|
export async function recordDone(sessionId, name, squarePath, options = {}) {
|
|
32
32
|
await writePresence(sessionId, name, squarePath, options, true);
|
|
@@ -64,17 +64,16 @@ function addLocalSession(identities, sessionId, channel, child, paseoAgentId) {
|
|
|
64
64
|
export function localSessionIdentities(env = process.env) { const paseoAgentId = env.PASEO_AGENT_ID?.trim() || undefined; const identities = []; for (const source of LOCAL_SESSION_SOURCES)
|
|
65
65
|
addLocalSession(identities, env[source.variable]?.trim(), source.channel, source.child !== undefined && env[source.child] === '1', paseoAgentId); addLocalSession(identities, paseoAgentId, 'paseo', false, paseoAgentId); return identities; }
|
|
66
66
|
export function hasAutomaticDeliveryIdentity(env = process.env) { return localSessionIdentities(env).length > 0; }
|
|
67
|
-
function
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
export async function recordLocalDone(name, squarePath, env = process.env) { const at = Date.now(); const current = await lookupParticipant(squarePath, name, at, env); for (const binding of current)
|
|
67
|
+
export async function recordLocalJoin(name, squarePath, env = process.env) { const at = Date.now(); const identities = localSessionIdentities(env); const current = await lookupParticipant(squarePath, name, at, env); for (const identity of identities) {
|
|
68
|
+
for (const binding of current.filter((item) => item.sessionId === identity.sessionId))
|
|
69
|
+
await recordDone(binding.sessionId, binding.name, binding.squarePath, { channel: binding.channel, at, env });
|
|
70
|
+
await recordJoin(identity.sessionId, name, squarePath, { ...identity, at, env });
|
|
71
|
+
} }
|
|
72
|
+
export async function recordLocalDone(name, squarePath, env = process.env) { const at = Date.now(); const identities = new Set(localSessionIdentities(env).map((identity) => identity.sessionId)); const current = (await lookupParticipant(squarePath, name, at, env)).filter((binding) => identities.has(binding.sessionId)); for (const binding of current)
|
|
74
73
|
await recordDone(binding.sessionId, binding.name, binding.squarePath, { channel: binding.channel, at, env }); }
|
|
75
|
-
export async function recordSessionJoin(sessionId, name, squarePath, channel, env = process.env) { const at = Date.now(); const current = await lookupParticipant(squarePath, name, at, env); for (const binding of current) {
|
|
74
|
+
export async function recordSessionJoin(sessionId, name, squarePath, channel, env = process.env) { const at = Date.now(); const current = (await lookupParticipant(squarePath, name, at, env)).filter((binding) => binding.sessionId === sessionId); for (const binding of current) {
|
|
76
75
|
await recordDone(binding.sessionId, binding.name, binding.squarePath, { channel: binding.channel, at, env });
|
|
77
76
|
await writePresence(binding.sessionId, binding.name, binding.squarePath, { channel: binding.channel, at, env }, true, 'user');
|
|
78
|
-
}
|
|
77
|
+
} await recordJoin(sessionId, name, squarePath, { channel, at, env }); await writePresence(sessionId, name, squarePath, { channel, at, env }, false, 'user'); return sessionId; }
|
|
79
78
|
export async function recordSessionDone(sessionId, name, squarePath, channel, env = process.env) { const canonicalPath = await canonicalSquarePath(squarePath); const binding = (await lookupSessionBindings(sessionId, Date.now(), env)).find((item) => item.squarePath === canonicalPath && sameName(item.name, name) && item.channel === channel); if (binding === undefined)
|
|
80
79
|
return false; const options = { channel, at: Date.now(), env }; await recordDone(sessionId, binding.name, binding.squarePath, options); await writePresence(sessionId, binding.name, binding.squarePath, options, true, 'user'); return true; }
|
package/dist/routes.d.ts
CHANGED
|
@@ -1,7 +1,26 @@
|
|
|
1
|
-
import { type WakeRoute } from './model.js';
|
|
1
|
+
import { type WakeRoute, type WakeRouteKind } from './model.js';
|
|
2
2
|
export { WAKE_ROUTE_KINDS } from './model.js';
|
|
3
3
|
export type { WakeRoute, WakeRouteKind } from './model.js';
|
|
4
4
|
export declare const ROUTE_FRESH_MS: number;
|
|
5
|
+
export type WakeBoundaryProvider = 'codex' | 'claude' | 'opencode' | 'pi' | 'paseo';
|
|
6
|
+
export interface WakeBoundary {
|
|
7
|
+
readonly location: string;
|
|
8
|
+
readonly participant: string;
|
|
9
|
+
readonly sessionId: string;
|
|
10
|
+
readonly provider: WakeBoundaryProvider;
|
|
11
|
+
}
|
|
12
|
+
export interface WakeRouteCapabilities {
|
|
13
|
+
readonly canUse: (kind: WakeRouteKind, address: Readonly<Record<string, string>>) => boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function defaultWakeRouteCapabilities(hostLedger?: import('./host-ledger.js').HostLedgerPort): Promise<WakeRouteCapabilities>;
|
|
16
|
+
/** Pure, ordered route precedence. Capability predicates own adapter and scope checks. */
|
|
17
|
+
export declare function selectPrimaryWakeRoute(input: {
|
|
18
|
+
readonly boundary: WakeBoundary;
|
|
19
|
+
readonly env: NodeJS.ProcessEnv;
|
|
20
|
+
readonly capabilities: WakeRouteCapabilities;
|
|
21
|
+
}): Omit<WakeRoute, 'updatedAt'> | undefined;
|
|
22
|
+
export declare function routeIdentityKey(route: Pick<WakeRoute, 'location' | 'participant' | 'sessionId'>, location?: string): string;
|
|
23
|
+
export declare function resolvePrimaryWakeRoute(boundary: WakeBoundary, env: NodeJS.ProcessEnv, capabilities: WakeRouteCapabilities): Omit<WakeRoute, 'updatedAt'> | undefined;
|
|
5
24
|
export declare function readWakeRoutes(opts?: {
|
|
6
25
|
location?: string;
|
|
7
26
|
participant?: string;
|
|
@@ -14,7 +33,12 @@ export declare function upsertWakeRoute(route: Omit<WakeRoute, 'updatedAt'>, opt
|
|
|
14
33
|
at?: number;
|
|
15
34
|
env?: NodeJS.ProcessEnv;
|
|
16
35
|
}): Promise<void>;
|
|
36
|
+
export declare function publishWakeRoute(artifact: import('./ports.js').SquareArtifactPort, route: Omit<WakeRoute, 'updatedAt'>, opts?: {
|
|
37
|
+
at?: number;
|
|
38
|
+
}): Promise<void>;
|
|
17
39
|
export declare function retireWakeRoute(route: WakeRoute, opts?: {
|
|
18
40
|
at?: number;
|
|
19
41
|
env?: NodeJS.ProcessEnv;
|
|
20
42
|
}): Promise<void>;
|
|
43
|
+
export declare function retireWakeRouteFromArtifact(artifact: import('./ports.js').SquareArtifactPort, route: Pick<WakeRoute, 'location' | 'participant' | 'sessionId'>): Promise<void>;
|
|
44
|
+
export declare function canonicalRouteLocation(location: string): Promise<string>;
|
package/dist/routes.js
CHANGED
|
@@ -1,9 +1,110 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { nameKey } from './model.js';
|
|
4
|
+
import { openSquare } from './square-file-adapter.js';
|
|
5
|
+
import { closeOpenSquare } from './open-square.js';
|
|
4
6
|
export { WAKE_ROUTE_KINDS } from './model.js';
|
|
5
7
|
export const ROUTE_FRESH_MS = 24 * 60 * 60 * 1000;
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
export async function defaultWakeRouteCapabilities(hostLedger) {
|
|
9
|
+
let userCapable = hostLedger !== undefined;
|
|
10
|
+
if (hostLedger !== undefined) {
|
|
11
|
+
try {
|
|
12
|
+
await hostLedger.listPresence({ scopes: ['user'], now: Date.now() });
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
userCapable = false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const available = new Set();
|
|
19
|
+
try {
|
|
20
|
+
const { CodexQueueAdapter } = await import('./codex-queue.js');
|
|
21
|
+
available.add(new CodexQueueAdapter().kind);
|
|
22
|
+
}
|
|
23
|
+
catch { /* optional */ }
|
|
24
|
+
try {
|
|
25
|
+
const { PaseoAdapter } = await import('./paseo-delivery.js');
|
|
26
|
+
available.add(new PaseoAdapter().kind);
|
|
27
|
+
}
|
|
28
|
+
catch { /* optional */ }
|
|
29
|
+
return { canUse: (kind, address) => userCapable && available.has(kind) && Object.values(address).every((value) => value.trim() !== '') };
|
|
30
|
+
}
|
|
31
|
+
function nativeCandidate(boundary) {
|
|
32
|
+
if (boundary.provider === 'codex')
|
|
33
|
+
return { kind: 'codex-queue', address: { threadId: boundary.sessionId } };
|
|
34
|
+
if (boundary.provider === 'claude')
|
|
35
|
+
return { kind: 'claude-native', address: { sessionId: boundary.sessionId } };
|
|
36
|
+
if (boundary.provider === 'opencode')
|
|
37
|
+
return { kind: 'opencode-server', address: { sessionId: boundary.sessionId } };
|
|
38
|
+
if (boundary.provider === 'pi')
|
|
39
|
+
return { kind: 'pi-extension', address: { sessionId: boundary.sessionId } };
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
/** Pure, ordered route precedence. Capability predicates own adapter and scope checks. */
|
|
43
|
+
export function selectPrimaryWakeRoute(input) {
|
|
44
|
+
const { boundary, env, capabilities } = input;
|
|
45
|
+
const paseoAgentId = env.PASEO_AGENT_ID?.trim();
|
|
46
|
+
const candidates = [];
|
|
47
|
+
if (paseoAgentId)
|
|
48
|
+
candidates.push({ kind: 'paseo', address: { agentId: paseoAgentId } });
|
|
49
|
+
const native = nativeCandidate(boundary);
|
|
50
|
+
if (native)
|
|
51
|
+
candidates.push(native);
|
|
52
|
+
const chosen = candidates.find((candidate) => Object.values(candidate.address).every((value) => value.trim() !== '') && capabilities.canUse(candidate.kind, candidate.address));
|
|
53
|
+
return chosen === undefined ? undefined : { location: boundary.location, participant: boundary.participant, sessionId: boundary.sessionId, channel: boundary.provider === 'paseo' ? 'paseo' : boundary.provider === 'claude' ? 'claude-code' : boundary.provider, ...chosen };
|
|
54
|
+
}
|
|
55
|
+
export function routeIdentityKey(route, location = route.location) {
|
|
56
|
+
return JSON.stringify([location, nameKey(route.participant), route.sessionId]);
|
|
57
|
+
}
|
|
58
|
+
export function resolvePrimaryWakeRoute(boundary, env, capabilities) {
|
|
59
|
+
return selectPrimaryWakeRoute({ boundary, env, capabilities });
|
|
60
|
+
}
|
|
61
|
+
async function withArtifact(location, fn) {
|
|
62
|
+
if (location === undefined)
|
|
63
|
+
return undefined;
|
|
64
|
+
try {
|
|
65
|
+
await fs.promises.access(location);
|
|
66
|
+
const square = await openSquare(location);
|
|
67
|
+
try {
|
|
68
|
+
return await fn(square);
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
await closeOpenSquare(square);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export async function readWakeRoutes(opts = {}) {
|
|
79
|
+
const now = opts.now ?? Date.now();
|
|
80
|
+
const canonicalLocation = opts.location === undefined ? undefined : await canonicalRouteLocation(opts.location);
|
|
81
|
+
const routes = await withArtifact(canonicalLocation, async (square) => (await square.artifact.read()).state.routes ?? []) ?? [];
|
|
82
|
+
const filtered = routes.filter((route) => (opts.participant === undefined || nameKey(route.participant) === nameKey(opts.participant)) && (opts.sessionId === undefined || route.sessionId === opts.sessionId) && (!opts.freshOnly || now - route.updatedAt < ROUTE_FRESH_MS));
|
|
83
|
+
const canonicalized = await Promise.all(filtered.map(async (route) => ({ ...route, location: await canonicalRouteLocation(route.location), address: { ...route.address } })));
|
|
84
|
+
return canonicalLocation === undefined ? canonicalized : canonicalized.filter((route) => route.location === canonicalLocation);
|
|
85
|
+
}
|
|
86
|
+
export async function upsertWakeRoute(route, opts = {}) {
|
|
87
|
+
const location = await canonicalRouteLocation(route.location);
|
|
88
|
+
await withArtifact(location, async (square) => publishWakeRoute(square.artifact, { ...route, location }, opts));
|
|
89
|
+
}
|
|
90
|
+
export async function publishWakeRoute(artifact, route, opts = {}) {
|
|
91
|
+
const location = await canonicalRouteLocation(route.location);
|
|
92
|
+
await artifact.transact((state) => ({ state: { ...state, routes: [...(state.routes ?? []).filter((item) => routeIdentityKey(item) !== routeIdentityKey({ ...route, location })), { ...route, location, participant: route.participant, updatedAt: opts.at ?? Date.now() }] }, result: undefined }));
|
|
93
|
+
}
|
|
94
|
+
export async function retireWakeRoute(route, opts = {}) {
|
|
95
|
+
const location = await canonicalRouteLocation(route.location);
|
|
96
|
+
await withArtifact(location, async (square) => retireWakeRouteFromArtifact(square.artifact, { ...route, location }));
|
|
97
|
+
}
|
|
98
|
+
export async function retireWakeRouteFromArtifact(artifact, route) {
|
|
99
|
+
const location = await canonicalRouteLocation(route.location);
|
|
100
|
+
await artifact.transact((state) => ({ state: { ...state, routes: (state.routes ?? []).filter((item) => routeIdentityKey(item) !== routeIdentityKey({ ...route, location })) }, result: undefined }));
|
|
101
|
+
}
|
|
102
|
+
export async function canonicalRouteLocation(location) {
|
|
103
|
+
const absolute = path.resolve(location);
|
|
104
|
+
try {
|
|
105
|
+
return await fs.promises.realpath(absolute);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return absolute;
|
|
109
|
+
}
|
|
110
|
+
}
|
package/dist/square-actions.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface OperationContext {
|
|
|
8
8
|
readonly clock: () => number;
|
|
9
9
|
readonly location?: string;
|
|
10
10
|
readonly hostLedger?: HostLedgerPort;
|
|
11
|
+
readonly wakeTransport?: import('./ports.js').WakeTransportPort;
|
|
11
12
|
readonly env?: NodeJS.ProcessEnv;
|
|
12
13
|
}
|
|
13
14
|
export declare function catchUp(square: OperationContext, name: string, options?: CatchOptions, project?: (state: SquareState) => CatchProjection): Promise<CatchResult>;
|
package/dist/square-actions.js
CHANGED
|
@@ -2,7 +2,9 @@ import { extractMentions, formatActivityId, parseActivityId } from './square-cor
|
|
|
2
2
|
import { coreDone, coreHold, coreIgnore, coreListen, coreListening, coreResume, decideAct, decideImplicitJoin, decideJoin } from './decisions.js';
|
|
3
3
|
import { SquareError } from './model.js';
|
|
4
4
|
import { participantIdentity } from './participant-identity.js';
|
|
5
|
+
import { deliverPending } from './delivery-operations.js';
|
|
5
6
|
import { decideCatch } from './catch-decisions.js';
|
|
7
|
+
import { publishWakeRoute, retireWakeRouteFromArtifact, resolvePrimaryWakeRoute, defaultWakeRouteCapabilities } from './routes.js';
|
|
6
8
|
function processIdentity(env) {
|
|
7
9
|
const choices = [
|
|
8
10
|
[env.CLAUDE_CODE_SESSION_ID, 'claude-code'], [env.CODEX_THREAD_ID, 'codex'],
|
|
@@ -11,6 +13,25 @@ function processIdentity(env) {
|
|
|
11
13
|
const found = choices.find(([session]) => session?.trim());
|
|
12
14
|
return found === undefined ? { session: `process:${process.pid}`, channel: 'unknown' } : { session: found[0].trim(), channel: found[1] };
|
|
13
15
|
}
|
|
16
|
+
async function publishIdentityRoute(context, participant) {
|
|
17
|
+
if (context.location === undefined || context.location === 'memory')
|
|
18
|
+
return;
|
|
19
|
+
const identity = processIdentity(context.env ?? process.env);
|
|
20
|
+
if (context.hostLedger === undefined)
|
|
21
|
+
return;
|
|
22
|
+
const provider = identity.channel === 'claude-code' ? 'claude' : identity.channel === 'opencode' ? 'opencode' : identity.channel === 'pi' ? 'pi' : identity.channel === 'paseo' ? 'paseo' : 'codex';
|
|
23
|
+
const capabilities = await defaultWakeRouteCapabilities(context.hostLedger);
|
|
24
|
+
const route = await resolvePrimaryWakeRoute({ location: context.location, participant, sessionId: identity.session, provider }, context.env ?? process.env, capabilities);
|
|
25
|
+
if (route === undefined)
|
|
26
|
+
return;
|
|
27
|
+
await publishWakeRoute(context.artifact, route, { at: context.clock() }).catch(() => undefined);
|
|
28
|
+
}
|
|
29
|
+
async function retireIdentityRoute(context, participant) {
|
|
30
|
+
if (context.location === undefined || context.location === 'memory')
|
|
31
|
+
return;
|
|
32
|
+
const identity = processIdentity(context.env ?? process.env);
|
|
33
|
+
await retireWakeRouteFromArtifact(context.artifact, { location: context.location, participant, sessionId: identity.session }).catch(() => undefined);
|
|
34
|
+
}
|
|
14
35
|
/** Presence is best effort and runs only after the artifact mutation commits. */
|
|
15
36
|
async function ensureLocalPresence(context, participant) {
|
|
16
37
|
if (context.hostLedger === undefined || context.location === undefined || context.location === 'memory')
|
|
@@ -48,6 +69,7 @@ export async function catchUp(square, name, options = {}, project) {
|
|
|
48
69
|
return { ...(decision.changed ? { state } : {}), result: { version, decision } };
|
|
49
70
|
});
|
|
50
71
|
await ensureLocalPresence(square, name);
|
|
72
|
+
await publishIdentityRoute(square, name);
|
|
51
73
|
if (attempt.decision.delivered.length > 0 || idle === 0) {
|
|
52
74
|
return {
|
|
53
75
|
activities: attempt.decision.delivered.map((activity) => exposeCaught(activity, attempt.decision.perceptions.get(activity.index) ?? 'full')),
|
|
@@ -103,6 +125,7 @@ export async function join(square, name) {
|
|
|
103
125
|
return { state, result: { name: decision.joinedName, stored: committedActivity(storeActs(state, [decision.joinAct]), 'join') } };
|
|
104
126
|
});
|
|
105
127
|
await ensureLocalPresence(square, committed.name);
|
|
128
|
+
await publishIdentityRoute(square, committed.name);
|
|
106
129
|
return { name: committed.name, activity: committed.stored === null ? null : exposeActivity(committed.stored) };
|
|
107
130
|
}
|
|
108
131
|
export async function implicitJoin(square, name) {
|
|
@@ -114,6 +137,10 @@ export async function implicitJoin(square, name) {
|
|
|
114
137
|
return { state, result: { name: decision.joinedName, state: decision.state, stored: committedActivity(storeActs(state, [decision.joinAct]), 'join') } };
|
|
115
138
|
});
|
|
116
139
|
await ensureLocalPresence(square, committed.name);
|
|
140
|
+
if (committed.state === 'done')
|
|
141
|
+
await retireIdentityRoute(square, committed.name);
|
|
142
|
+
else
|
|
143
|
+
await publishIdentityRoute(square, committed.name);
|
|
117
144
|
return { name: committed.name, state: committed.state, activity: committed.stored === null ? null : exposeActivity(committed.stored) };
|
|
118
145
|
}
|
|
119
146
|
export async function express(square, name, body, options = {}) {
|
|
@@ -139,7 +166,15 @@ export async function express(square, name, body, options = {}) {
|
|
|
139
166
|
return { state, result: { stored } };
|
|
140
167
|
});
|
|
141
168
|
await ensureLocalPresence(square, name);
|
|
142
|
-
|
|
169
|
+
await publishIdentityRoute(square, name);
|
|
170
|
+
let delivery;
|
|
171
|
+
if (square.wakeTransport !== undefined && square.hostLedger !== undefined && square.location !== undefined && square.location !== 'memory') {
|
|
172
|
+
delivery = await deliverPending({ artifact: square.artifact, hostLedger: square.hostLedger, transport: square.wakeTransport, location: square.location, activity: committed.stored.index, now }).catch(() => ({ attempted: 0, accepted: 0, failed: 0, unknown: 0, notCapable: 1 }));
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
delivery = { attempted: 0, accepted: 0, failed: 0, unknown: 0, notCapable: 1 };
|
|
176
|
+
}
|
|
177
|
+
return { activity: exposeActivity(committed.stored), delivery };
|
|
143
178
|
}
|
|
144
179
|
async function landListenerChange(square, verb, actor, target) {
|
|
145
180
|
const now = square.clock();
|
|
@@ -160,6 +195,8 @@ async function landCore(square, verb, actor, body = '') {
|
|
|
160
195
|
const act = verb === 'done' ? coreDone(state, actor, body, now) : verb === 'hold' ? coreHold(state, actor, body, now) : coreResume(state, actor, now);
|
|
161
196
|
return { state, result: committedActivity(storeActs(state, [act]), verb) };
|
|
162
197
|
});
|
|
198
|
+
if (verb === 'done')
|
|
199
|
+
await retireIdentityRoute(square, actor);
|
|
163
200
|
return { activity: exposeActivity(stored) };
|
|
164
201
|
}
|
|
165
202
|
export function done(square, name, body = '') { return landCore(square, 'done', name, body); }
|
package/dist/square-facade.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ActivityId, Perception, Reach } from './square-core.js';
|
|
2
|
-
import type { HostLedgerPort } from './ports.js';
|
|
2
|
+
import type { HostLedgerPort, WakeTransportPort, DeliveryResult } from './ports.js';
|
|
3
3
|
export interface Activity {
|
|
4
4
|
readonly id: ActivityId;
|
|
5
5
|
readonly at: number;
|
|
@@ -20,6 +20,7 @@ export interface ExpressOptions {
|
|
|
20
20
|
}
|
|
21
21
|
export interface ExpressResult {
|
|
22
22
|
readonly activity: Activity;
|
|
23
|
+
readonly delivery?: DeliveryResult;
|
|
23
24
|
}
|
|
24
25
|
export interface ListenerChangeResult {
|
|
25
26
|
readonly activity: Activity | null;
|
|
@@ -68,6 +69,7 @@ export type SquareSource = {
|
|
|
68
69
|
export interface OpenOptions {
|
|
69
70
|
clock?: () => number;
|
|
70
71
|
hostLedger?: HostLedgerPort;
|
|
72
|
+
wakeTransport?: WakeTransportPort;
|
|
71
73
|
env?: NodeJS.ProcessEnv;
|
|
72
74
|
}
|
|
73
75
|
export interface SquareAtInput extends SquareSource, OpenOptions {
|
|
@@ -78,6 +80,7 @@ export interface SquareBuildInput extends SquareSource {
|
|
|
78
80
|
throttlePerMinute?: number;
|
|
79
81
|
clock?: () => number;
|
|
80
82
|
hostLedger?: HostLedgerPort;
|
|
83
|
+
wakeTransport?: WakeTransportPort;
|
|
81
84
|
env?: NodeJS.ProcessEnv;
|
|
82
85
|
}
|
|
83
86
|
export interface Participant {
|
|
@@ -11,9 +11,10 @@ export interface SquareBuildOptions {
|
|
|
11
11
|
throttlePerMinute?: number;
|
|
12
12
|
clock?: () => number;
|
|
13
13
|
hostLedger?: HostLedgerPort;
|
|
14
|
+
wakeTransport?: import('./ports.js').WakeTransportPort;
|
|
14
15
|
env?: NodeJS.ProcessEnv;
|
|
15
16
|
}
|
|
16
|
-
export declare function openSquare(squarePath: string, options?: Pick<SquareBuildOptions, 'clock' | 'hostLedger' | 'env'>): Promise<OpenSquare>;
|
|
17
|
+
export declare function openSquare(squarePath: string, options?: Pick<SquareBuildOptions, 'clock' | 'hostLedger' | 'wakeTransport' | 'env'>): Promise<OpenSquare>;
|
|
17
18
|
export declare function probeSquare(squarePath: string): Promise<OpenSquare | undefined>;
|
|
18
19
|
export declare function buildSquare(squarePath: string, options: SquareBuildOptions): Promise<OpenSquare>;
|
|
19
20
|
export declare function buildMemorySquare(options: SquareBuildOptions): OpenSquare;
|